├── src └── mod │ ├── hooks │ ├── Hooks.h │ └── Hooks.cpp │ ├── forms │ ├── AddRankForm.h │ ├── SetRankForm.h │ ├── EditRankForm.h │ ├── SetRankForm.cpp │ ├── AddRankForm.cpp │ └── EditRankForm.cpp │ ├── MemoryOperators.cpp │ ├── Main.h │ ├── manager │ ├── base │ │ ├── BaseManager.h │ │ └── BaseManager.cpp │ ├── lang │ │ ├── LanguageManager.h │ │ └── LanguageManager.cpp │ ├── command │ │ ├── CommandManager.h │ │ └── CommandManager.cpp │ ├── config │ │ ├── ConfigManager.cpp │ │ └── ConfigManager.h │ ├── MainManager.h │ ├── ranks │ │ ├── RanksManager.h │ │ └── RanksManager.cpp │ ├── rankFormats │ │ ├── RankFormatsManager.h │ │ └── RankFormatsManager.cpp │ └── MainManager.cpp │ ├── types │ ├── HiddenCommandOverloads.h │ ├── Rank.cpp │ ├── HiddenCommandOverloads.cpp │ └── Rank.h │ ├── Api.h │ ├── commands │ ├── RemoveRankCommand.h │ ├── SetRankCommand.h │ ├── AddRankCommand.h │ ├── RemoveRankCommand.cpp │ ├── EditRankCommand.h │ ├── AddRankCommand.cpp │ ├── SetRankCommand.cpp │ └── EditRankCommand.cpp │ ├── Main.cpp │ ├── utils │ ├── Utils.h │ └── Utils.cpp │ └── Api.cpp ├── .clangd ├── CHANGELOG.md ├── manifest.json ├── assets └── data │ ├── config.json │ ├── rankFormats │ ├── ru_RU.json │ └── en_US.json │ ├── ranks.json │ └── languages │ ├── en_US.json │ └── ru_RU.json ├── .github ├── PULL_REQUEST_TEMPLATE.md ├── ISSUE_TEMPLATE │ ├── feature_request.yml │ └── bug_report.yml └── workflows │ ├── build.yml │ └── release.yml ├── .clang-format ├── tooth.json ├── README.ru.md ├── README.md ├── xmake.lua ├── .clang-tidy └── LICENSE /src/mod/hooks/Hooks.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace power_ranks::hooks { 4 | 5 | void setupHooks(); 6 | 7 | } // namespace power_ranks::hooks -------------------------------------------------------------------------------- /src/mod/forms/AddRankForm.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace power_ranks::forms { 6 | 7 | class AddRankForm { 8 | public: 9 | static void init(Player& player); 10 | }; 11 | 12 | } // namespace power_ranks::forms -------------------------------------------------------------------------------- /src/mod/forms/SetRankForm.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace power_ranks::forms { 6 | 7 | class SetRankForm { 8 | public: 9 | static void init(Player& player); 10 | }; 11 | 12 | } // namespace power_ranks::forms -------------------------------------------------------------------------------- /.clangd: -------------------------------------------------------------------------------- 1 | Diagnostics: 2 | Suppress: 3 | - "-Wmicrosoft-enum-forward-reference" 4 | - "-Wc++11-narrowing" 5 | - "-Wc++2b-extensions" 6 | - "-Wmicrosoft-cast" 7 | CompileFlags: 8 | Add: 9 | - "-ferror-limit=0" 10 | - '-D__FUNCTION__="dummy"' 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | -------------------------------------------------------------------------------- /src/mod/MemoryOperators.cpp: -------------------------------------------------------------------------------- 1 | // This file will make your mod use LeviLamina's memory operators by default. 2 | // This improves the memory management of your mod and is recommended to use. 3 | 4 | #define LL_MEMORY_OPERATORS 5 | 6 | #include // IWYU pragma: keep 7 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "${modName}", 3 | "entry": "${modFile}", 4 | "version": "${modVersion}", 5 | "type": "native", 6 | "dependencies": [ 7 | { 8 | "name": "TranslatorApi" 9 | }, 10 | { 11 | "name": "PlayerDB" 12 | } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /src/mod/forms/EditRankForm.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../types/Rank.h" 4 | #include 5 | 6 | namespace power_ranks::forms { 7 | 8 | class EditRankForm { 9 | public: 10 | static void init(Player& player, types::Rank* rank, const std::string& localeCode); 11 | }; 12 | 13 | } // namespace power_ranks::forms -------------------------------------------------------------------------------- /assets/data/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "defaultLocaleCode": "en_US", 4 | "defaultRankName": "Default", 5 | "ranksWithColoredMessages": [ 6 | "Helper", 7 | "Administrator" 8 | ], 9 | "superRanks": [ 10 | "Helper", 11 | "Administrator" 12 | ], 13 | "superPlayers": [ 14 | "LordBombardir" 15 | ] 16 | } -------------------------------------------------------------------------------- /src/mod/Main.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace power_ranks { 6 | 7 | class Main { 8 | 9 | public: 10 | static Main& getInstance(); 11 | 12 | Main() : mSelf(*ll::mod::NativeMod::current()) {} 13 | 14 | [[nodiscard]] ll::mod::NativeMod& getSelf() const { return mSelf; } 15 | 16 | bool load(); 17 | bool enable(); 18 | bool disable(); 19 | 20 | private: 21 | ll::mod::NativeMod& mSelf; 22 | }; 23 | 24 | } // namespace power_ranks 25 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## What does this PR do? 2 | 3 | 4 | 5 | ## Which issues does this PR resolve? 6 | 7 | 8 | 9 | ## Checklist before merging 10 | 11 | Thank you for your contribution to the repository. 12 | Before submitting this PR, please make sure: 13 | 14 | - [ ] Your code builds clean without any errors or warnings 15 | - [ ] Your code follows [LeviLamina C++ Style Guide](https://github.com/LiteLDev/LeviLamina/wiki/CPP-Style-Guide) 16 | - [ ] You have tested all functions 17 | - [ ] You have not used code without license 18 | - [ ] You have added statement for third-party code 19 | -------------------------------------------------------------------------------- /src/mod/manager/base/BaseManager.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace power_ranks::manager { 10 | 11 | class BaseManager final { 12 | public: 13 | static void init(ll::mod::NativeMod& mod); 14 | 15 | static std::optional getPlayerRank(const mce::UUID& uuid); 16 | static bool setPlayerRank(const mce::UUID& uuid, const std::string& rankName); 17 | 18 | private: 19 | static std::unique_ptr database; 20 | }; 21 | 22 | } // namespace power_ranks::manager -------------------------------------------------------------------------------- /src/mod/manager/base/BaseManager.cpp: -------------------------------------------------------------------------------- 1 | #include "BaseManager.h" 2 | 3 | namespace power_ranks::manager { 4 | 5 | std::unique_ptr BaseManager::database = {}; 6 | 7 | void BaseManager::init(ll::mod::NativeMod& mod) { 8 | const auto& path = mod.getDataDir() / "base"; 9 | database = std::make_unique(path); 10 | } 11 | 12 | std::optional BaseManager::getPlayerRank(const mce::UUID& uuid) { return database->get(uuid.asString()); } 13 | 14 | bool BaseManager::setPlayerRank(const mce::UUID& uuid, const std::string& rankName) { 15 | return database->set(uuid.asString(), rankName); 16 | } 17 | 18 | 19 | } // namespace power_ranks::manager -------------------------------------------------------------------------------- /src/mod/manager/lang/LanguageManager.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../config/ConfigManager.h" 4 | #include 5 | #include 6 | 7 | namespace power_ranks::manager { 8 | 9 | class LanguageManager final { 10 | public: 11 | static void init(ll::mod::NativeMod& mod); 12 | 13 | static std::string getTranslate( 14 | const std::string_view& key, 15 | const std::string_view& localeCode = ConfigManager::getConfig().defaultLocaleCode 16 | ); 17 | 18 | static void addTranslations(); 19 | 20 | private: 21 | LanguageManager() = default; 22 | ~LanguageManager() = default; 23 | 24 | static std::unique_ptr i18n; 25 | }; 26 | 27 | } // namespace power_ranks::manager -------------------------------------------------------------------------------- /src/mod/manager/command/CommandManager.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace power_ranks::manager { 9 | 10 | class CommandManager final { 11 | public: 12 | enum class RankNames; 13 | static constexpr inline std::string_view rankEnumNames = ll::command::enum_name_v; 14 | 15 | static bool registerCommands(); 16 | static void addRankNameToSoftEnum(const std::string& rankName); 17 | static void removeRankNameFromSoftEnum(const std::string& rankName); 18 | 19 | static bool 20 | isCommandAvailable(const CommandOrigin& origin, CommandFlag flags, CommandPermissionLevel permissionLevel); 21 | }; 22 | 23 | } // namespace power_ranks::manager -------------------------------------------------------------------------------- /src/mod/manager/config/ConfigManager.cpp: -------------------------------------------------------------------------------- 1 | #include "ConfigManager.h" 2 | #include 3 | 4 | namespace power_ranks::manager { 5 | 6 | ConfigManager::MainConfig ConfigManager::config; 7 | 8 | bool ConfigManager::init(ll::mod::NativeMod& mod) { 9 | const auto& pathToConfig = mod.getDataDir() / "config.json"; 10 | 11 | try { 12 | return ll::config::loadConfig(config, pathToConfig); 13 | } catch (const std::exception& e) { 14 | mod.getLogger().error("Failed to load config: {}", e.what()); 15 | } 16 | 17 | try { 18 | return ll::config::saveConfig(config, pathToConfig); 19 | } catch (const std::exception& e) { 20 | mod.getLogger().error("Failed to save config: {}", e.what()); 21 | return false; 22 | } 23 | } 24 | 25 | const ConfigManager::MainConfig& ConfigManager::getConfig() { return config; } 26 | 27 | } // namespace power_ranks::manager -------------------------------------------------------------------------------- /src/mod/types/HiddenCommandOverloads.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace power_ranks::types { 8 | 9 | struct HiddenCommandOverloads { 10 | public: 11 | HiddenCommandOverloads() = default; 12 | 13 | explicit HiddenCommandOverloads(const std::unordered_map>& data) 14 | : data(data) {} 15 | 16 | // Format: teleport:0,2,3,1;gamemode:1 17 | std::string toString() const; 18 | void updateFromString(const std::string& rawData); 19 | 20 | const std::unordered_map>& getData() const { return data; }; 21 | 22 | private: 23 | std::unordered_map> data = {}; 24 | 25 | static std::unordered_map> getDataFromString(const std::string& rawData); 26 | }; 27 | 28 | } // namespace power_ranks::types -------------------------------------------------------------------------------- /assets/data/rankFormats/ru_RU.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "ranks": { 4 | "Default": { 5 | "prefix": "Игрок", 6 | "chat": "{prefix} {playerName}: {message}", 7 | "scoreTag": "{prefix}" 8 | }, 9 | "Vip": { 10 | "prefix": "ВИП", 11 | "chat": "{prefix} {playerName}: §b{message}", 12 | "scoreTag": "{prefix}" 13 | }, 14 | "Creative": { 15 | "prefix": "Творец", 16 | "chat": "{prefix} {playerName}: §a{message}", 17 | "scoreTag": "{prefix}" 18 | }, 19 | "Helper": { 20 | "prefix": "Зам. босса", 21 | "chat": "{prefix} {playerName}: §l{message}", 22 | "scoreTag": "{prefix}" 23 | }, 24 | "Administrator": { 25 | "prefix": "Босс", 26 | "chat": "{prefix} {playerName}: §c§l{message}", 27 | "scoreTag": "{prefix}" 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /assets/data/rankFormats/en_US.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "ranks": { 4 | "Default": { 5 | "prefix": "Player", 6 | "chat": "{prefix} {playerName}: {message}", 7 | "scoreTag": "{prefix}" 8 | }, 9 | "Vip": { 10 | "prefix": "V.I.P.", 11 | "chat": "{prefix} {playerName}: §b{message}", 12 | "scoreTag": "{prefix}" 13 | }, 14 | "Creative": { 15 | "prefix": "Creator", 16 | "chat": "{prefix} {playerName}: §a{message}", 17 | "scoreTag": "{prefix}" 18 | }, 19 | "Helper": { 20 | "prefix": "Mini-boss", 21 | "chat": "{prefix} {playerName}: §l{message}", 22 | "scoreTag": "{prefix}" 23 | }, 24 | "Administrator": { 25 | "prefix": "BOSS", 26 | "chat": "{prefix} {playerName}: §c§l{message}", 27 | "scoreTag": "{prefix}" 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /src/mod/manager/config/ConfigManager.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace power_ranks::manager { 8 | 9 | class ConfigManager final { 10 | public: 11 | struct MainConfig { 12 | int version = 3; 13 | std::string defaultLocaleCode = "en_US"; 14 | std::string defaultRankName = "Default"; 15 | std::unordered_set ranksWithColoredMessages = {"Helper", "Administrator"}; 16 | std::unordered_set superRanks = {"Helper", "Administrator"}; 17 | std::unordered_set superPlayers = {"LordBombardir"}; 18 | }; 19 | 20 | static bool init(ll::mod::NativeMod& mod); 21 | static const MainConfig& getConfig(); 22 | 23 | private: 24 | static MainConfig config; 25 | }; 26 | 27 | } // namespace power_ranks::manager -------------------------------------------------------------------------------- /src/mod/Api.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "types/Rank.h" 4 | #include 5 | #include 6 | 7 | #ifndef POWERRANKS_API 8 | #ifdef POWERRANKS_EXPORT 9 | #define POWERRANKS_API __declspec(dllexport) 10 | #else 11 | #define POWERRANKS_API __declspec(dllimport) 12 | #endif 13 | #endif 14 | 15 | extern "C++" { 16 | 17 | namespace power_ranks::api { 18 | 19 | POWERRANKS_API std::unordered_map getRanks(); 20 | POWERRANKS_API std::optional getRank(const std::string& name); 21 | 22 | POWERRANKS_API const types::Rank& getPlayerRankOrSetDefault(Player& player); 23 | POWERRANKS_API const types::Rank& getPlayerRankOrSetDefault(const std::string& playerName); 24 | 25 | POWERRANKS_API void setPlayerRank(Player& player, const types::Rank& rank); 26 | POWERRANKS_API void setPlayerRankByName(const std::string& playerName, const types::Rank& rank); 27 | POWERRANKS_API void setPlayerRankByXuid(const std::string& xuid, const types::Rank& rank); 28 | 29 | } // namespace power_ranks::api 30 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: Feature request 2 | description: Suggest an idea for this project 3 | title: "[Feature]: " 4 | labels: ["enhancement"] 5 | body: 6 | - type: textarea 7 | attributes: 8 | label: Is your feature request related to a problem? Please describe. 9 | description: A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 10 | validations: 11 | required: true 12 | 13 | - type: textarea 14 | attributes: 15 | label: Describe the solution you'd like 16 | description: A clear and concise description of what you want to happen. 17 | validations: 18 | required: true 19 | 20 | - type: textarea 21 | attributes: 22 | label: Describe alternatives you've considered 23 | description: A clear and concise description of any alternative solutions or features you've considered. 24 | 25 | - type: textarea 26 | attributes: 27 | label: Additional context 28 | description: Add any other context or screenshots about the feature request here. 29 | -------------------------------------------------------------------------------- /src/mod/manager/MainManager.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../types/Rank.h" 4 | #include 5 | #include 6 | #include 7 | 8 | namespace power_ranks::manager { 9 | 10 | class MainManager final { 11 | public: 12 | static bool initManagers(ll::mod::NativeMod& mod); 13 | static void disposeManagers(); 14 | 15 | static const types::Rank& getPlayerRankOrSetDefault(Player& player); 16 | static const types::Rank& getPlayerRankOrSetDefault(const std::string& playerName); 17 | 18 | static void setPlayerRank(Player& player, const types::Rank& rank); 19 | static void setPlayerRankByName(const std::string& playerName, const types::Rank& rank); 20 | static void setPlayerRankByXuid(const std::string& xuid, const types::Rank& rank); 21 | 22 | static void updatePlayerRank(Player& player); 23 | 24 | private: 25 | static void setScoreTag(Player& player, const std::string& scoreTag); 26 | 27 | static void extraActions(const types::Rank& rank, const Player& player); 28 | static void extraVanillaActions(Player& player, const types::Rank& rank); 29 | }; 30 | 31 | } // namespace power_ranks::manager -------------------------------------------------------------------------------- /assets/data/ranks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "ranks": { 4 | "Default": { 5 | "inheritanceRank": "null", 6 | "availableCommands": [], 7 | "hiddenCommandOverloads": {}, 8 | "additionalInformation": [] 9 | }, 10 | "Vip": { 11 | "inheritanceRank": "Default", 12 | "availableCommands": [ 13 | "home", 14 | "warp" 15 | ], 16 | "hiddenCommandOverloads": {}, 17 | "additionalInformation": [] 18 | }, 19 | "Creative": { 20 | "inheritanceRank": "Vip", 21 | "availableCommands": [ 22 | "gamemode" 23 | ], 24 | "hiddenCommandOverloads": {}, 25 | "additionalInformation": [] 26 | }, 27 | "Helper": { 28 | "inheritanceRank": "Creative", 29 | "availableCommands": [ 30 | "kick", 31 | "mute" 32 | ], 33 | "hiddenCommandOverloads": {}, 34 | "additionalInformation": [] 35 | }, 36 | "Administrator": { 37 | "inheritanceRank": "Helper", 38 | "availableCommands": [ 39 | "ban" 40 | ], 41 | "hiddenCommandOverloads": {}, 42 | "additionalInformation": [] 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: LLVM 2 | AccessModifierOffset: -4 3 | AlignAfterOpenBracket: BlockIndent 4 | AlignArrayOfStructures: Left 5 | AlignConsecutiveDeclarations: 6 | Enabled: true 7 | AcrossEmptyLines: false 8 | AcrossComments: false 9 | AlignConsecutiveAssignments: 10 | Enabled: true 11 | AcrossEmptyLines: false 12 | AcrossComments: false 13 | AlignCompound: true 14 | PadOperators: true 15 | AlignConsecutiveMacros: 16 | Enabled: true 17 | AcrossEmptyLines: false 18 | AcrossComments: false 19 | AllowAllParametersOfDeclarationOnNextLine: false 20 | AllowAllArgumentsOnNextLine: false 21 | AlignOperands: AlignAfterOperator 22 | AlignConsecutiveBitFields: 23 | Enabled: true 24 | AcrossEmptyLines: false 25 | AcrossComments: false 26 | AllowShortLambdasOnASingleLine: All 27 | AllowShortBlocksOnASingleLine: Empty 28 | AllowShortIfStatementsOnASingleLine: AllIfsAndElse 29 | AllowShortLoopsOnASingleLine: true 30 | AlwaysBreakAfterDefinitionReturnType: None 31 | AlwaysBreakTemplateDeclarations: 'Yes' 32 | BinPackArguments: false 33 | BinPackParameters: false 34 | BreakBeforeBraces: Custom 35 | BreakBeforeBinaryOperators: NonAssignment 36 | ColumnLimit: 120 37 | CommentPragmas: '^ IWYU pragma:' 38 | ConstructorInitializerIndentWidth: 0 39 | IndentWidth: 4 40 | Language: Cpp 41 | MaxEmptyLinesToKeep: 2 42 | PackConstructorInitializers: CurrentLine 43 | PointerAlignment: Left 44 | TabWidth: 4 45 | UseTab: Never 46 | SortIncludes: CaseSensitive -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | on: 2 | pull_request: 3 | push: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | build: 8 | runs-on: windows-latest 9 | steps: 10 | - uses: actions/checkout@v4 11 | 12 | - uses: xmake-io/github-action-setup-xmake@v1 13 | # with: 14 | # xmake-version: branch@master 15 | 16 | - uses: actions/cache@v4 17 | with: 18 | path: | 19 | ~/AppData/Local/.xmake 20 | key: xmake-${{ hashFiles('xmake.lua') }} 21 | restore-keys: | 22 | xmake- 23 | 24 | - run: | 25 | xmake repo -u 26 | 27 | - run: | 28 | xmake f -a x64 -m debug -p windows -v -y 29 | 30 | - run: | 31 | xmake -v -y 32 | 33 | - uses: actions/upload-artifact@v4 34 | with: 35 | name: ${{ github.event.repository.name }}-windows-x64-${{ github.sha }} 36 | path: | 37 | bin/ 38 | 39 | # clang-format: 40 | # runs-on: windows-latest 41 | # steps: 42 | # - uses: actions/checkout@v4 43 | 44 | # - run: | 45 | # choco install llvm -y --version=17.0.6 46 | 47 | # - name: clang-format 48 | # run: | 49 | # Get-ChildItem src/ -Filter *.cpp -Recurse | ForEach-Object { clang-format --dry-run -Werror $_.FullName; if ($LASTEXITCODE -ne 0) { exit 1; } } 50 | # Get-ChildItem src/ -Filter *.h -Recurse | ForEach-Object { clang-format --dry-run -Werror $_.FullName; if ($LASTEXITCODE -ne 0) { exit 1; } } 51 | -------------------------------------------------------------------------------- /src/mod/commands/RemoveRankCommand.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../manager/command/CommandManager.h" 4 | #include "../manager/lang/LanguageManager.h" 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | namespace power_ranks::commands { 13 | 14 | class RemoveRankCommand { 15 | public: 16 | struct Parameter { 17 | ll::command::SoftEnum rankName; 18 | }; 19 | 20 | static std::string getName() { return "removerank"; }; 21 | static std::string getDescription() { 22 | return manager::LanguageManager::getTranslate("commandRemoveRankDescription"); 23 | }; 24 | static CommandPermissionLevel getRequirement() { return CommandPermissionLevel::GameDirectors; }; 25 | static CommandFlag getFlag() { return CommandFlagValue::NotCheat; }; 26 | 27 | static std::vector getAliases() { return {"remove-rank"}; }; 28 | 29 | static void execute( 30 | const CommandOrigin& origin, 31 | CommandOutput& output, 32 | const Parameter& parameter, 33 | [[maybe_unused]] const Command& command 34 | ); 35 | static void executeWithoutParameter(const CommandOrigin& origin, CommandOutput& output); 36 | }; 37 | 38 | } // namespace power_ranks::commands -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: Create a report to help us improve 3 | title: "[Bug]: " 4 | labels: ["bug"] 5 | body: 6 | - type: textarea 7 | attributes: 8 | label: Describe the bug 9 | description: A clear and concise description of what the bug is. 10 | validations: 11 | required: true 12 | 13 | - type: textarea 14 | attributes: 15 | label: To Reproduce 16 | description: Steps to reproduce the behavior. 17 | validations: 18 | required: true 19 | 20 | - type: textarea 21 | attributes: 22 | label: Expected behavior 23 | description: A clear and concise description of what you expected to happen. 24 | validations: 25 | required: true 26 | 27 | - type: textarea 28 | attributes: 29 | label: Screenshots 30 | description: If applicable, add screenshots to help explain your problem. 31 | 32 | - type: input 33 | attributes: 34 | label: Platform 35 | description: The platform you are using. (e.g. Windows 10) 36 | 37 | - type: input 38 | attributes: 39 | label: BDS Version 40 | description: The version of BDS you are using. (e.g. 1.20.32.1) 41 | 42 | - type: input 43 | attributes: 44 | label: LeviLamina Version 45 | description: The version of LeviLamina you are using. (e.g. 1.0.0) 46 | 47 | - type: input 48 | attributes: 49 | label: Version 50 | description: The version of the mod you are using. (e.g. 1.0.0) 51 | 52 | - type: textarea 53 | attributes: 54 | label: Additional context 55 | description: Add any other context about the problem here. 56 | -------------------------------------------------------------------------------- /src/mod/commands/SetRankCommand.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../manager/command/CommandManager.h" 4 | #include "../manager/lang/LanguageManager.h" 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | namespace power_ranks::commands { 15 | 16 | class SetRankCommand { 17 | public: 18 | struct Parameter { 19 | ll::command::SoftEnum rankName; 20 | CommandSelector player; 21 | }; 22 | 23 | static std::string getName() { return "setrank"; }; 24 | static std::string getDescription() { return manager::LanguageManager::getTranslate("commandSetRankDescription"); }; 25 | static CommandPermissionLevel getRequirement() { return CommandPermissionLevel::GameDirectors; }; 26 | static CommandFlag getFlag() { return CommandFlagValue::NotCheat; }; 27 | 28 | static std::vector getAliases() { return {"set-rank"}; }; 29 | 30 | static void execute( 31 | const CommandOrigin& origin, 32 | CommandOutput& output, 33 | const Parameter& parameter, 34 | [[maybe_unused]] const Command& command 35 | ); 36 | static void executeWithoutParameter(const CommandOrigin& origin, CommandOutput& output); 37 | }; 38 | 39 | } // namespace power_ranks::commands -------------------------------------------------------------------------------- /src/mod/Main.cpp: -------------------------------------------------------------------------------- 1 | #include "Main.h" 2 | #include "hooks/Hooks.h" 3 | #include "manager/MainManager.h" 4 | #include "manager/command/CommandManager.h" 5 | #include "manager/lang/LanguageManager.h" 6 | #include 7 | 8 | namespace power_ranks { 9 | 10 | Main& Main::getInstance() { 11 | static Main instance; 12 | return instance; 13 | } 14 | 15 | bool Main::load() { 16 | getSelf().getLogger().info("The mod is loading..."); 17 | 18 | if (!manager::MainManager::initManagers(getSelf())) { 19 | getSelf().getLogger().info("Failed to load the mod!"); 20 | return false; 21 | } 22 | 23 | hooks::setupHooks(); 24 | 25 | getSelf().getLogger().info("The mod has been successfully loaded!"); 26 | return true; 27 | } 28 | 29 | bool Main::enable() { 30 | getSelf().getLogger().info("The mod is enabling..."); 31 | 32 | if (!manager::CommandManager::registerCommands()) { 33 | getSelf().getLogger().info("Failed to enable the mod!"); 34 | return false; 35 | } 36 | 37 | getSelf().getLogger().info( 38 | "The mod has been successfully enabled! Choosed language: " 39 | + manager::LanguageManager::getTranslate("languageName") 40 | ); 41 | 42 | getSelf().getLogger().info("Author: vk.com/lordbomba"); 43 | return true; 44 | } 45 | 46 | bool Main::disable() { 47 | getSelf().getLogger().info("The mod is disabling..."); 48 | 49 | manager::MainManager::disposeManagers(); 50 | 51 | getSelf().getLogger().info("The mod has been successfully disabled."); 52 | return true; 53 | } 54 | 55 | } // namespace power_ranks 56 | 57 | LL_REGISTER_MOD(power_ranks::Main, power_ranks::Main::getInstance()); 58 | -------------------------------------------------------------------------------- /src/mod/utils/Utils.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | namespace power_ranks { 6 | 7 | class Utils final { 8 | public: 9 | static std::string clean(const std::string& input, bool removeFormat = true); 10 | 11 | static std::string 12 | strReplace(const std::string& originalStr, std::string_view whatNeedToReplace, std::string_view whatForReplace); 13 | 14 | static std::string strReplace( 15 | const std::string& originalStr, 16 | const std::vector& whatNeedToReplace, 17 | const std::vector& whatForReplace 18 | ); 19 | 20 | static std::vector strSplit(std::string_view str, std::string_view separator); 21 | 22 | template 23 | requires std::ranges::range && requires(const Container& c) { c.size(); } 24 | && std::convertible_to, std::string_view> 25 | static std::string separateContainer(const Container& container, std::string_view separator) { 26 | if (container.empty()) { 27 | return ""; 28 | } 29 | 30 | std::string result; 31 | for (auto [index, str] : std::views::enumerate(container)) { 32 | result += str; 33 | if (static_cast(index) + 1 < container.size()) { 34 | result += separator; 35 | } 36 | } 37 | 38 | return result; 39 | } 40 | 41 | static std::string strTrim(std::string_view str); 42 | 43 | private: 44 | // Helper to remove Unicode codepoints in range U+E000 to U+F8FF 45 | static std::string removePrivateUseUnicode(std::string_view input); 46 | }; 47 | 48 | } // namespace power_ranks -------------------------------------------------------------------------------- /src/mod/manager/lang/LanguageManager.cpp: -------------------------------------------------------------------------------- 1 | #include "LanguageManager.h" 2 | #include "../../commands/AddRankCommand.h" 3 | #include "../../commands/EditRankCommand.h" 4 | #include "../../commands/RemoveRankCommand.h" 5 | #include "../../commands/SetRankCommand.h" 6 | #include 7 | #include 8 | 9 | namespace power_ranks::manager { 10 | 11 | std::unique_ptr LanguageManager::i18n = nullptr; 12 | 13 | void LanguageManager::init(ll::mod::NativeMod& mod) { 14 | i18n = std::make_unique(); 15 | auto _ = i18n->load(mod.getDataDir() / "languages"); 16 | } 17 | 18 | std::string LanguageManager::getTranslate(const std::string_view& key, const std::string_view& localeCode) { 19 | return static_cast(i18n->get(key, localeCode)); 20 | } 21 | 22 | void LanguageManager::addTranslations() { 23 | translator::api::setPlaceholder( 24 | commands::AddRankCommand::getName(), 25 | manager::LanguageManager::getTranslate("commandAddRankDescription", "ru_RU"), 26 | "ru_RU" 27 | ); 28 | translator::api::setPlaceholder( 29 | commands::EditRankCommand::getName(), 30 | manager::LanguageManager::getTranslate("commandEditRankDescription", "ru_RU"), 31 | "ru_RU" 32 | ); 33 | translator::api::setPlaceholder( 34 | commands::RemoveRankCommand::getName(), 35 | manager::LanguageManager::getTranslate("commandRemoveRankDescription", "ru_RU"), 36 | "ru_RU" 37 | ); 38 | translator::api::setPlaceholder( 39 | commands::SetRankCommand::getName(), 40 | manager::LanguageManager::getTranslate("commandSetRankDescription", "ru_RU"), 41 | "ru_RU" 42 | ); 43 | } 44 | 45 | } // namespace power_ranks::manager -------------------------------------------------------------------------------- /src/mod/Api.cpp: -------------------------------------------------------------------------------- 1 | #include "Api.h" 2 | #include "manager/MainManager.h" 3 | #include "manager/ranks/RanksManager.h" 4 | #include 5 | 6 | namespace power_ranks::api { 7 | 8 | std::unordered_map getRanks() { 9 | const std::unordered_map& originalMap = manager::RanksManager::getRanks(); 10 | 11 | std::unordered_map newMap; 12 | newMap.reserve(originalMap.size()); 13 | 14 | for (const auto& [key, value] : originalMap) { 15 | newMap.emplace(key, value); 16 | } 17 | 18 | return newMap; 19 | } 20 | 21 | std::optional getRank(const std::string& name) { return manager::RanksManager::getRank(name); } 22 | 23 | const types::Rank& getPlayerRankOrSetDefault(Player& player) { 24 | return manager::MainManager::getPlayerRankOrSetDefault(player); 25 | } 26 | 27 | const types::Rank& getPlayerRankOrSetDefault(const std::string& playerName) { 28 | return manager::MainManager::getPlayerRankOrSetDefault(playerName); 29 | } 30 | 31 | void setPlayerRank(Player& player, const types::Rank& rank) { manager::MainManager::setPlayerRank(player, rank); } 32 | 33 | void setPlayerRankByName(const std::string& playerName, const types::Rank& rank) { 34 | if (playerName.empty()) { 35 | throw std::invalid_argument("Parameter «playerName» must not be empty!"); 36 | } 37 | 38 | manager::MainManager::setPlayerRankByName(playerName, rank); 39 | } 40 | 41 | void setPlayerRankByXuid(const std::string& xuid, const types::Rank& rank) { 42 | if (xuid.empty()) { 43 | throw std::invalid_argument("Parameter «xuid» must not be empty!"); 44 | } 45 | 46 | manager::MainManager::setPlayerRankByXuid(xuid, rank); 47 | } 48 | 49 | } // namespace power_ranks::api 50 | -------------------------------------------------------------------------------- /src/mod/commands/AddRankCommand.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../manager/command/CommandManager.h" 4 | #include "../manager/lang/LanguageManager.h" 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | namespace power_ranks::commands { 13 | 14 | class AddRankCommand { 15 | public: 16 | struct Parameter { 17 | std::string rankName; 18 | std::string prefix; 19 | std::string chat; 20 | std::string scoreTag; 21 | ll::command::SoftEnum inheritanceRank = "null"; 22 | }; 23 | 24 | static std::string getName() { return "addrank"; }; 25 | static std::string getDescription() { return manager::LanguageManager::getTranslate("commandAddRankDescription"); }; 26 | static CommandPermissionLevel getRequirement() { return CommandPermissionLevel::GameDirectors; }; 27 | static CommandFlag getFlag() { return CommandFlagValue::NotCheat; }; 28 | 29 | static std::vector getAliases() { return {"add-rank"}; }; 30 | 31 | static void execute( 32 | const CommandOrigin& origin, 33 | CommandOutput& output, 34 | const Parameter& parameter, 35 | [[maybe_unused]] const Command& command 36 | ); 37 | static void executeWithoutParameter(const CommandOrigin& origin, CommandOutput& output); 38 | }; 39 | 40 | } // namespace power_ranks::commands -------------------------------------------------------------------------------- /tooth.json: -------------------------------------------------------------------------------- 1 | { 2 | "format_version": 3, 3 | "format_uuid": "289f771f-2c9a-4d73-9f3f-8492495a924d", 4 | "tooth": "github.com/LordBombardir/LLPowerRanks", 5 | "version": "1.1.4", 6 | "info": { 7 | "name": "PowerRanks", 8 | "description": "A mod that adds a rank (privilege) system to the game", 9 | "tags": [ 10 | "platform:levilamina", 11 | "type:mod" 12 | ] 13 | }, 14 | "variants": [ 15 | { 16 | "label": "", 17 | "platform": "win-x64", 18 | "dependencies": { 19 | "github.com/LiteLDev/LeviLamina": ">=1.6.0", 20 | "github.com/LordBombardir/LLTranslatorApi": ">=1.2.8", 21 | "github.com/LordBombardir/LLPlayerDB-Release": ">=1.1.0" 22 | }, 23 | "assets": [ 24 | { 25 | "type": "zip", 26 | "urls": [ 27 | "https://{{tooth}}/releases/download/v{{version}}/LLPowerRanks-windows-x64.zip" 28 | ], 29 | "placements": [ 30 | { 31 | "type": "dir", 32 | "src": "PowerRanks/", 33 | "dest": "plugins/PowerRanks/" 34 | } 35 | ] 36 | } 37 | ], 38 | "preserve_files": [], 39 | "remove_files": [], 40 | "scripts": { 41 | "pre_install": [], 42 | "install": [], 43 | "post_install": [], 44 | "pre_pack": [], 45 | "post_pack": [], 46 | "pre_uninstall": [], 47 | "uninstall": [], 48 | "post_uninstall": [] 49 | } 50 | } 51 | ] 52 | } 53 | -------------------------------------------------------------------------------- /src/mod/types/Rank.cpp: -------------------------------------------------------------------------------- 1 | #include "Rank.h" 2 | 3 | namespace power_ranks::types { 4 | 5 | std::optional Rank::getAdditionalInformation(size_t index) const { 6 | if (index < additionalInformation.size()) { 7 | return additionalInformation[index]; 8 | } 9 | 10 | return std::nullopt; 11 | } 12 | 13 | void Rank::setInheritanceRank(const Rank* inheritanceRank) { this->inheritanceRank = inheritanceRank; } 14 | 15 | void Rank::setAvailableCommands(const std::unordered_set& availableCommands) { 16 | this->availableCommands = availableCommands; 17 | } 18 | 19 | void Rank::setAdditionalInformation(const std::vector& additionalInformation) { 20 | this->additionalInformation = additionalInformation; 21 | } 22 | 23 | bool Rank::isCommandAvailable(const std::string& name) const { 24 | if (availableCommands.contains(name)) { 25 | return true; 26 | } 27 | 28 | if (inheritanceRank.has_value()) { 29 | return inheritanceRank.value()->isCommandAvailable(name); 30 | } 31 | 32 | return false; 33 | } 34 | 35 | bool Rank::isCommandOverloadHidden(const std::string& name, int overloadIndex) const { 36 | auto it = hiddenCommandOverloads.getData().find(name); 37 | if (it == hiddenCommandOverloads.getData().end()) { 38 | return false; 39 | } 40 | 41 | return it->second.contains(overloadIndex); 42 | } 43 | 44 | void Rank::removeInheritanceRank() { inheritanceRank = std::nullopt; } 45 | 46 | bool Rank::operator<(const Rank& other) const { return priority < other.priority; } 47 | 48 | bool Rank::operator<=(const Rank& other) const { return priority <= other.priority; } 49 | 50 | bool Rank::operator>(const Rank& other) const { return priority > other.priority; } 51 | 52 | bool Rank::operator>=(const Rank& other) const { return priority >= other.priority; } 53 | 54 | } // namespace power_ranks::types -------------------------------------------------------------------------------- /src/mod/types/HiddenCommandOverloads.cpp: -------------------------------------------------------------------------------- 1 | #include "HiddenCommandOverloads.h" 2 | #include "../utils/Utils.h" 3 | 4 | namespace power_ranks::types { 5 | 6 | std::string HiddenCommandOverloads::toString() const { 7 | std::unordered_set commandEntries; 8 | 9 | for (const auto& [command, overloads] : data) { 10 | std::unordered_set overloadStrs; 11 | for (int overload : overloads) { 12 | overloadStrs.insert(std::to_string(overload)); 13 | } 14 | 15 | const auto& joinedOverloads = Utils::separateContainer(overloadStrs, ","); 16 | commandEntries.insert(command + ":" + joinedOverloads); 17 | } 18 | 19 | return Utils::separateContainer(commandEntries, ";"); 20 | } 21 | 22 | void HiddenCommandOverloads::updateFromString(const std::string& rawData) { data = getDataFromString(rawData); } 23 | 24 | std::unordered_map> 25 | HiddenCommandOverloads::getDataFromString(const std::string& rawData) { 26 | std::unordered_map> result; 27 | 28 | const auto& commandParts = Utils::strSplit(rawData, ";"); 29 | for (const auto& entry : commandParts) { 30 | const auto& parts = Utils::strSplit(entry, ":"); 31 | if (parts.size() != 2) { 32 | continue; 33 | } 34 | 35 | const auto& command = parts[0]; 36 | const auto& overloadStrings = Utils::strSplit(parts[1], ","); 37 | 38 | std::unordered_set overloads; 39 | for (const auto& str : overloadStrings) { 40 | if (!str.empty()) { 41 | overloads.insert(std::stoi(str)); 42 | } 43 | } 44 | 45 | if (overloads.empty()) { 46 | continue; 47 | } 48 | 49 | result[command] = std::move(overloads); 50 | } 51 | 52 | return result; 53 | } 54 | 55 | } // namespace power_ranks::types -------------------------------------------------------------------------------- /README.ru.md: -------------------------------------------------------------------------------- 1 | [![Английский](https://custom-icon-badges.demolab.com/badge/-Английский-green?style=for-the-badge)](README.md) [![Русский](https://custom-icon-badges.demolab.com/badge/-Русский-gray?style=for-the-badge)](README.ru.md) 2 | 3 | # Что делает этот мод? 4 | Данный мод позволяет владельцам серверов [BDS](https://www.minecraft.net/ru-ru/download/server/bedrock) под управлением [LeviLamina](https://github.com/LiteLDev/LeviLamina) создавать ранги (привилегии), с помощью которых можно определить игроку доступные ему команды. Также он обеспечивает форматирование игрового чата и *скортэга* игрока. 5 | # Управление модом 6 | Управление модом осуществляется посредством внесения изменений в конфигурации. Выдать игроку ранг можно как в консоли, так и в игре посредством команды `setrank`. 7 | ## Форматирование чата 8 | Для каждого ранга можно сделать свой формат отправки игроком сообщения при помощи конфигурации рангов. Для этого необходимо изменить поле *chat* желаемого ранга. Значение (текст) этого поля поддерживает следующие переменные: 9 | - Префикс ранга: *{prefix}* 10 | - Никнейм игрока: *{playerName}* 11 | - Сообщение, отправляемое игроком в чат: *{message}* 12 | ## Форматирование *скортэга* игрока 13 | Для каждого ранга можно сделать свой формат *скортэга* игрока. Для этого необходимо изменить поле *scoreTag* желаемого ранга. Значение (текст) этого поля поддерживает лишь одну переменную *{prefix}* (префикс ранга) 14 | # Что под капотом? 15 | А под капотом у нас: 16 | * Оптимизированный и высокоскоростной код на *C++* 17 | * Менеджер конфигурации языков (используется [i18n](https://github.com/LiteLDev/LeviLamina/blob/develop/src/ll/api/i18n/)). Папка с языками - *languages* 18 | * Менеджер конфигурации рангов типа *JSON*. Название файла конфигурации - *ranks.json* 19 | * Менеджер главной конфигурации мода типа *JSON*. Название файла конфигурации - *config.json* 20 | * Менеджер управления базой данных типа *LevelDB*. В ней хранятся данные игроков, а именно - UUID игрока, предоставленный [PlayerDB](https://github.com/LordBombardir/LLPlayerDB-Release), и его ранг 21 | * Главный менеджер мода, регистрирующий команды `addrank` и `setrank`, а также управляющий выдачей ранга игроку 22 | * Хуки, один из которых изменяет содержимое пакета `AvailableCommandsPacket` при его отправке 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![English](https://custom-icon-badges.demolab.com/badge/-English-gray?style=for-the-badge)](README.md) [![Russian](https://custom-icon-badges.demolab.com/badge/-Russian-green?style=for-the-badge)](README.ru.md) 2 | 3 | # What does this mod do? 4 | This mod allows owners of [BDS](https://www.minecraft.net/en-us/download/server/bedrock) managed by [LeviLamina](https://github.com/LiteLDev/LeviLamina) create ranks (privileges) with which you can determine the commands available to the player. It also provides formatting for the player's game chat and *scoretag*. 5 | # Mod Management 6 | The mod is controlled by making configuration changes. You can assign a rank to a player both in the console and in the game using the `setrank` command. 7 | ## Formatting the chat 8 | For each rank, you can make your own format for sending a message by the player using the rank configuration. To do this, you need to change the *chat* field of the desired rank. The value (text) of this field supports the following variables: 9 | - Rank prefix: *{prefix}* 10 | - Player's nickname: *{playerName}* 11 | - A message sent by the player to the chat: *{message}* 12 | ## Formatting of the player's *scoretag* 13 | For each rank, you can make your own player's *scoretag* format. To do this, you need to change the *scoreTag* field of the desired rank. The value (text) of this field supports only one variable *{prefix}* (rank prefix) 14 | # What's under the hood? 15 | And under the hood we have: 16 | * Optimized and high-speed code in *C++* 17 | * Language Configuration Manager (used by [i18n](https://github.com/LiteLDev/LeviLamina/blob/develop/src/ll/api/i18n)). Folder with languages - *languages* 18 | * *JSON* type rank configuration manager. The name of the configuration file is *ranks.json* 19 | * Manager of the main configuration of the *JSON* type mod. The name of the configuration file is *config.json* 20 | * Database management manager of the *LevelDB* type. It stores player data, namely, the player's UUID, provided by [PlayerDB](https://github.com/LordBombardir/LLPlayerDB-Release), and his rank 21 | * The main manager of the mod, registering the `addrank` and `setrank` commands, as well as managing the issue of rank to the player 22 | * Hooks, one of which modifies the contents of the `AvailableCommandsPacket` when it is sent 23 | -------------------------------------------------------------------------------- /src/mod/commands/RemoveRankCommand.cpp: -------------------------------------------------------------------------------- 1 | #include "RemoveRankCommand.h" 2 | #include "../utils/Utils.h" 3 | #include "../manager/ranks/RanksManager.h" 4 | #include 5 | 6 | namespace power_ranks::commands { 7 | 8 | void RemoveRankCommand::execute( 9 | const CommandOrigin& origin, 10 | CommandOutput& output, 11 | const Parameter& parameter, 12 | [[maybe_unused]] const Command& _ 13 | ) { 14 | const auto& localeCode = origin.getEntity() == nullptr || !origin.getEntity()->isType(ActorType::Player) 15 | ? manager::ConfigManager::getConfig().defaultLocaleCode 16 | : static_cast(*origin.getEntity()).getLocaleCode(); 17 | 18 | const auto& rank = manager::RanksManager::getRank(parameter.rankName); 19 | if (!rank.has_value() || rank.value() == nullptr) { 20 | std::string ranks; 21 | for (const auto& [name, otherRank] : manager::RanksManager::getRanks()) { 22 | if (ranks.empty()) { 23 | ranks = name; 24 | continue; 25 | } 26 | 27 | ranks += ", " + name; 28 | } 29 | 30 | output.error(Utils::strReplace( 31 | manager::LanguageManager::getTranslate("undefinedRank", localeCode), 32 | {"{rankName}", "{ranks}"}, 33 | {parameter.rankName, std::move(ranks)} 34 | )); 35 | return; 36 | } 37 | 38 | manager::RanksManager::removeRank(*rank.value()); 39 | output.success(Utils::strReplace( 40 | manager::LanguageManager::getTranslate("commandRemoveRankSuccess", localeCode), 41 | "{rankName}", 42 | parameter.rankName 43 | )); 44 | } 45 | 46 | void RemoveRankCommand::executeWithoutParameter(const CommandOrigin& origin, CommandOutput& output) { 47 | const auto& localeCode = origin.getEntity() == nullptr || !origin.getEntity()->isType(ActorType::Player) 48 | ? manager::ConfigManager::getConfig().defaultLocaleCode 49 | : static_cast(*origin.getEntity()).getLocaleCode(); 50 | 51 | output.error(manager::LanguageManager::getTranslate("commandRemoveRankUsing", localeCode)); 52 | } 53 | 54 | } // namespace power_ranks::commands -------------------------------------------------------------------------------- /xmake.lua: -------------------------------------------------------------------------------- 1 | add_rules("mode.debug", "mode.release") 2 | 3 | add_repositories("liteldev-repo https://github.com/LiteLDev/xmake-repo.git") 4 | add_repositories("lordbombardir-repo https://github.com/LordBombardir/xmake-repo.git") 5 | 6 | -- add_requires("levilamina x.x.x") for a specific version 7 | -- add_requires("levilamina develop") to use develop version 8 | -- please note that you should add bdslibrary yourself if using dev version 9 | add_requires("levilamina 1.6.0") 10 | add_requires("translatorapi 1.2.8") 11 | add_requires("playerdb 1.1.0") 12 | add_requires("nlohmann_json") 13 | add_requires("levibuildscript") 14 | 15 | if not has_config("vs_runtime") then 16 | set_runtimes("MD") 17 | end 18 | 19 | target("PowerRanks") -- Change this to your mod name. 20 | add_rules("@levibuildscript/linkrule") 21 | add_rules("@levibuildscript/modpacker") 22 | 23 | add_cxflags("/EHa", "/utf-8", "/W4", "/w44265", "/w44289", "/w44296", "/w45263", "/w44738", "/w45204") 24 | add_defines("NOMINMAX", "UNICODE", "_HAS_CXX23=1", "POWERRANKS_EXPORT") 25 | 26 | add_packages("levilamina") 27 | add_packages("translatorapi") 28 | add_packages("playerdb") 29 | add_packages("nlohmann_json") 30 | 31 | set_exceptions("none") -- To avoid conflicts with /EHa. 32 | set_kind("shared") 33 | set_languages("c++20") 34 | set_symbols("debug") 35 | 36 | add_headerfiles("src/**.h") 37 | add_files("src/**.cpp") 38 | add_includedirs("src") 39 | 40 | after_build(function (target) 41 | local binDirectory = path.join(os.projectdir(), "bin") 42 | 43 | local libDirectory = path.join(binDirectory, "lib") 44 | local includeDirectory = path.join(path.join(binDirectory, "include"), "power_ranks") 45 | local typesDirectory = path.join(includeDirectory, "types") 46 | 47 | os.mkdir(libDirectory) 48 | os.mkdir(includeDirectory) 49 | os.mkdir(typesDirectory) 50 | 51 | os.cp(path.join(target:targetdir(), "PowerRanks.lib"), libDirectory) 52 | os.cp(path.join(os.projectdir(), "src", "mod", "Api.h"), includeDirectory) 53 | os.cp(path.join(os.projectdir(), "src", "mod", "types", "Rank.h"), typesDirectory) 54 | os.cp(path.join(os.projectdir(), "src", "mod", "types", "HiddenCommandOverloads.h"), typesDirectory) 55 | os.cp(path.join(os.projectdir(), "assets", "data"), path.join(path.join(binDirectory, target:name()), "data")) 56 | end) 57 | -------------------------------------------------------------------------------- /src/mod/manager/ranks/RanksManager.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../../types/Rank.h" 4 | #include "../config/ConfigManager.h" 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | namespace power_ranks::manager { 12 | 13 | class RanksManager final { 14 | public: 15 | struct Rank { 16 | std::string inheritanceRank = "null"; 17 | std::unordered_set availableCommands = {}; 18 | std::unordered_map> hiddenCommandOverloads = {}; 19 | std::vector additionalInformation = {}; 20 | }; 21 | 22 | // clang-format off 23 | struct Config { 24 | int version = 3; 25 | nlohmann::ordered_map ranks = { 26 | {ConfigManager::getConfig().defaultRankName, {}}, 27 | {"Vip", {ConfigManager::getConfig().defaultRankName, {"home", "warp"}}}, 28 | {"Creative", {"Vip", {"gamemode"}}}, 29 | {"Helper", {"Creative", {"kick", "mute"}}}, 30 | {"Administrator", {"Helper", {"ban"}}}, 31 | }; 32 | }; 33 | // clang-format on 34 | 35 | static bool init(ll::mod::NativeMod& mod); 36 | static void dispose(); 37 | 38 | static nlohmann::ordered_map getOrderedRanks(); 39 | 40 | static std::unordered_map getRanks(); 41 | static std::optional getRank(const std::string& name); 42 | 43 | static void addRank( 44 | const std::string& name, 45 | const std::string& prefix, 46 | const std::string& chatFormat, 47 | const std::string& scoreTagFormat, 48 | const std::optional& inheritanceRank = std::nullopt 49 | ); 50 | static void removeRank(const types::Rank& rank); 51 | static void saveChangesRank(const types::Rank& rank); 52 | 53 | private: 54 | static int currentPriority; 55 | static void parseRanks(); 56 | 57 | static std::filesystem::path pathToConfig; 58 | static Config config; 59 | 60 | static std::unordered_map ranks; 61 | }; 62 | 63 | } // namespace power_ranks::manager -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | on: 2 | release: 3 | types: 4 | - published 5 | 6 | jobs: 7 | build: 8 | runs-on: windows-latest 9 | steps: 10 | - uses: actions/checkout@v4 11 | 12 | - uses: xmake-io/github-action-setup-xmake@v1 13 | # with: 14 | # xmake-version: branch@master 15 | 16 | - uses: actions/cache@v4 17 | with: 18 | path: | 19 | ~/AppData/Local/.xmake 20 | key: xmake-${{ hashFiles('xmake.lua') }} 21 | restore-keys: | 22 | xmake- 23 | 24 | - run: | 25 | xmake repo -u 26 | 27 | - run: | 28 | xmake f -a x64 -m release -p windows -y 29 | 30 | - run: | 31 | xmake -v -y 32 | 33 | - uses: actions/upload-artifact@v4 34 | with: 35 | name: ${{ github.event.repository.name }}-windows-x64-${{ github.sha }} 36 | path: | 37 | bin/ 38 | 39 | update-release-notes: 40 | permissions: 41 | contents: write 42 | runs-on: ubuntu-latest 43 | steps: 44 | - uses: actions/checkout@v4 45 | 46 | - id: extract-release-notes 47 | uses: ffurrer2/extract-release-notes@v2 48 | 49 | - uses: softprops/action-gh-release@v1 50 | with: 51 | body: | 52 | ${{ steps.extract-release-notes.outputs.release_notes }} 53 | 54 | upload-to-release: 55 | needs: 56 | - build 57 | - update-release-notes 58 | permissions: 59 | contents: write 60 | runs-on: ubuntu-latest 61 | steps: 62 | - uses: actions/checkout@v4 63 | 64 | - uses: actions/download-artifact@v4 65 | with: 66 | name: ${{ github.event.repository.name }}-windows-x64-${{ github.sha }} 67 | path: artifact 68 | 69 | - run: | 70 | cp CHANGELOG.md LICENSE README.md artifact/ 71 | 72 | - run: | 73 | zip -r ../${{ github.event.repository.name }}-windows-x64.zip * 74 | working-directory: artifact 75 | 76 | - name: Calculate SHA256 77 | id: calculate-sha256 78 | run: | 79 | echo release=$(sha256sum ${{ github.event.repository.name }}-windows-x64.zip | awk '{print $1}') >> $GITHUB_OUTPUT 80 | 81 | - uses: softprops/action-gh-release@v1 82 | with: 83 | append_body: true 84 | body: | 85 | | File | SHA256 | 86 | | ---- | ------ | 87 | | ${{ github.event.repository.name }}-windows-x64.zip | ${{ steps.calculate-sha256.outputs.release }} | 88 | files: | 89 | ${{ github.event.repository.name }}-windows-x64.zip 90 | -------------------------------------------------------------------------------- /src/mod/commands/EditRankCommand.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../manager/command/CommandManager.h" 4 | #include "../manager/lang/LanguageManager.h" 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | namespace power_ranks::commands { 13 | 14 | class EditRankCommand { 15 | public: 16 | struct FirstParameter { 17 | ll::command::SoftEnum rankName; 18 | std::string localeCode; 19 | }; 20 | 21 | struct SecondParameter { 22 | ll::command::SoftEnum rankName; 23 | std::string prefix; 24 | std::string chat; 25 | std::string scoreTag; 26 | std::string localeCode; 27 | ll::command::SoftEnum inheritanceRank; 28 | std::string availableCommands; 29 | std::string hiddenCommandOverloads; 30 | std::string additionalInformation; 31 | }; 32 | 33 | static std::string getName() { return "editrank"; }; 34 | static std::string getDescription() { 35 | return manager::LanguageManager::getTranslate("commandEditRankDescription"); 36 | }; 37 | static CommandPermissionLevel getRequirement() { return CommandPermissionLevel::GameDirectors; }; 38 | static CommandFlag getFlag() { return CommandFlagValue::NotCheat; }; 39 | 40 | static std::vector getAliases() { return {"edit-rank"}; }; 41 | 42 | static void executeFirstParameter( 43 | const CommandOrigin& origin, 44 | CommandOutput& output, 45 | const FirstParameter& parameter, 46 | [[maybe_unused]] const Command& command 47 | ); 48 | static void executeSecondParameter( 49 | const CommandOrigin& origin, 50 | CommandOutput& output, 51 | const SecondParameter& parameter, 52 | [[maybe_unused]] const Command& command 53 | ); 54 | 55 | static void executeWithoutParameter(const CommandOrigin& origin, CommandOutput& output); 56 | }; 57 | 58 | } // namespace power_ranks::commands -------------------------------------------------------------------------------- /src/mod/types/Rank.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "HiddenCommandOverloads.h" 4 | #include 5 | 6 | namespace power_ranks::types { 7 | 8 | class Rank final { 9 | public: 10 | Rank( 11 | const int priority, 12 | const std::string& name, 13 | const std::optional& inheritanceRank = std::nullopt, 14 | const std::unordered_set& availableCommands = {}, 15 | const HiddenCommandOverloads& hiddenCommandOverloads = {}, 16 | const std::vector additionalInformation = {} 17 | ) 18 | : priority(priority), 19 | name(name), 20 | inheritanceRank(inheritanceRank), 21 | availableCommands(availableCommands), 22 | hiddenCommandOverloads(hiddenCommandOverloads), 23 | additionalInformation(additionalInformation) {} 24 | ~Rank() = default; 25 | 26 | std::string getName() const { return name; } 27 | const std::optional& getInheritanceRank() const { return inheritanceRank; } 28 | const std::unordered_set& getAvailableCommands() const { return availableCommands; } 29 | const HiddenCommandOverloads& getHiddenCommandOverloads() const { return hiddenCommandOverloads; } 30 | HiddenCommandOverloads& getHiddenCommandOverloads() { return hiddenCommandOverloads; } 31 | const std::vector& getAdditionalInformation() const { return additionalInformation; } 32 | 33 | std::optional getAdditionalInformation(size_t index) const; 34 | 35 | void setInheritanceRank(const Rank* inheritanceRank); 36 | void setAvailableCommands(const std::unordered_set& availableCommands); 37 | void setAdditionalInformation(const std::vector& additionalInformation); 38 | 39 | bool isCommandAvailable(const std::string& name) const; 40 | bool isCommandOverloadHidden(const std::string& name, int overloadIndex) const; 41 | 42 | void removeInheritanceRank(); 43 | 44 | bool operator<(const Rank& other) const; 45 | bool operator<=(const Rank& other) const; 46 | bool operator>(const Rank& other) const; 47 | bool operator>=(const Rank& other) const; 48 | 49 | private: 50 | Rank() = delete; 51 | 52 | const int priority; 53 | const std::string name; 54 | 55 | std::optional inheritanceRank; 56 | std::unordered_set availableCommands; 57 | HiddenCommandOverloads hiddenCommandOverloads; 58 | std::vector additionalInformation; 59 | }; 60 | 61 | } // namespace power_ranks::types -------------------------------------------------------------------------------- /src/mod/commands/AddRankCommand.cpp: -------------------------------------------------------------------------------- 1 | #include "AddRankCommand.h" 2 | #include "../forms/AddRankForm.h" 3 | #include "../manager/ranks/RanksManager.h" 4 | #include "../utils/Utils.h" 5 | #include 6 | 7 | namespace power_ranks::commands { 8 | 9 | void AddRankCommand::execute( 10 | const CommandOrigin& origin, 11 | CommandOutput& output, 12 | const Parameter& parameter, 13 | [[maybe_unused]] const Command& _ 14 | ) { 15 | const auto& localeCode = origin.getEntity() == nullptr || !origin.getEntity()->isType(ActorType::Player) 16 | ? manager::ConfigManager::getConfig().defaultLocaleCode 17 | : static_cast(*origin.getEntity()).getLocaleCode(); 18 | 19 | if (manager::RanksManager::getRank(parameter.rankName).has_value()) { 20 | output.error(manager::LanguageManager::getTranslate("addRankAlreadyExists", localeCode)); 21 | return; 22 | } 23 | 24 | const auto& inheritanceRank = manager::RanksManager::getRank(parameter.inheritanceRank); 25 | if (parameter.inheritanceRank != "null" && !inheritanceRank.has_value()) { 26 | std::string ranks = ""; 27 | for (const auto& [name, rank] : manager::RanksManager::getRanks()) { 28 | if (ranks.empty()) { 29 | ranks = name; 30 | continue; 31 | } 32 | 33 | ranks += ", " + name; 34 | } 35 | 36 | output.error( 37 | Utils::strReplace( 38 | manager::LanguageManager::getTranslate("undefinedRank", localeCode), 39 | {"{rankName}", "{ranks}"}, 40 | {parameter.rankName, std::move(ranks)} 41 | ) 42 | ); 43 | return; 44 | } 45 | 46 | manager::RanksManager::addRank( 47 | parameter.rankName, 48 | parameter.prefix, 49 | parameter.chat, 50 | parameter.scoreTag, 51 | inheritanceRank 52 | ); 53 | output.success( 54 | Utils::strReplace( 55 | manager::LanguageManager::getTranslate("addRankSuccess", localeCode), 56 | "{rankName}", 57 | parameter.rankName 58 | ) 59 | ); 60 | } 61 | 62 | void AddRankCommand::executeWithoutParameter(const CommandOrigin& origin, CommandOutput& output) { 63 | if (origin.getEntity() == nullptr || !origin.getEntity()->isType(ActorType::Player)) { 64 | output.error(manager::LanguageManager::getTranslate("commandAddRankUsing")); 65 | return; 66 | } 67 | 68 | forms::AddRankForm::init(static_cast(*origin.getEntity())); 69 | } 70 | 71 | } // namespace power_ranks::commands -------------------------------------------------------------------------------- /src/mod/manager/rankFormats/RankFormatsManager.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../config/ConfigManager.h" 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | namespace power_ranks::manager { 11 | 12 | class RankFormatsManager final { 13 | public: 14 | static bool init(ll::mod::NativeMod& mod); 15 | 16 | static bool isKnownLocaleCode(std::string_view localeCode, bool supportAll = false); 17 | 18 | static std::string getRawPrefixFormat(const std::string& rankName, const std::string& localeCode); 19 | static std::string getRawChatFormat(const std::string& rankName, const std::string& localeCode); 20 | static std::string getRawScoreTagFormat(const std::string& rankName, const std::string& localeCode); 21 | 22 | static std::string getPrefixFormat(const std::string& rankName); 23 | static std::string getScoreTagFormat(const std::string& rankName); 24 | 25 | static std::string 26 | getChatFormat(const std::string& rankName, const std::string& playerName, const std::string& message); 27 | 28 | static void setRankFormat( 29 | const std::string& rankName, 30 | const std::string& prefix, 31 | const std::string& chat, 32 | const std::string& scoreTag, 33 | std::string_view localeCode = ConfigManager::getConfig().defaultLocaleCode 34 | ); 35 | 36 | static void removeRankFormat(const std::string& rankName); 37 | 38 | private: 39 | struct RankFormat { 40 | std::string prefix; 41 | std::string chat; 42 | std::string scoreTag; 43 | }; 44 | 45 | // clang-format off 46 | struct Config { 47 | int version = 1; 48 | nlohmann::ordered_map ranks = { 49 | {ConfigManager::getConfig().defaultRankName, {"Player", "{prefix} {playerName}: {message}", "{prefix}"}}, 50 | {"Vip", {"V.I.P.", "{prefix} {playerName}: §b{message}", "{prefix}"}}, 51 | {"Creative", {"Creator", "{prefix} {playerName}: §a{message}", "{prefix}"}}, 52 | {"Helper", {"Mini-boss", "{prefix} {playerName}: §l{message}", "{prefix}"}}, 53 | {"Administrator", {"BOSS", "{prefix} {playerName}: §c§l{message}", "{prefix}"}}, 54 | }; 55 | }; 56 | // clang-format on 57 | 58 | struct ConfigInfo { 59 | std::filesystem::path pathToConfig; 60 | Config config; 61 | }; 62 | 63 | static std::unordered_map configs; 64 | 65 | static void generatePlaceholders(); 66 | 67 | static ConfigInfo& getConfig(std::string_view localeCode = ConfigManager::getConfig().defaultLocaleCode); 68 | }; 69 | 70 | } // namespace power_ranks::manager -------------------------------------------------------------------------------- /src/mod/forms/SetRankForm.cpp: -------------------------------------------------------------------------------- 1 | #include "SetRankForm.h" 2 | #include "../manager/MainManager.h" 3 | #include "../manager/lang/LanguageManager.h" 4 | #include "../manager/rankFormats/RankFormatsManager.h" 5 | #include "../manager/ranks/RanksManager.h" 6 | #include "../utils/Utils.h" 7 | #include 8 | #include 9 | 10 | namespace power_ranks::forms { 11 | 12 | void SetRankForm::init(Player& player) { 13 | ll::form::CustomForm form(manager::LanguageManager::getTranslate("formSetRankTitle", player.getLocaleCode())); 14 | 15 | std::vector playerNames = {}; 16 | player.getLevel().forEachPlayer([&playerNames](Player& otherPlayer) -> bool { 17 | playerNames.push_back(otherPlayer.getRealName()); 18 | return true; 19 | }); 20 | 21 | form.appendDropdown( 22 | "playerName", 23 | manager::LanguageManager::getTranslate("formSetRankDropdownPlayers", player.getLocaleCode()), 24 | playerNames 25 | ); 26 | 27 | nlohmann::ordered_map ranks = {}; 28 | for (const auto& [name, rank] : manager::RanksManager::getOrderedRanks()) { 29 | ranks[std::format("{} - {}", name, manager::RankFormatsManager::getPrefixFormat(rank->getName()))] = rank; 30 | } 31 | 32 | std::vector rankNames = {}; 33 | for (const auto& rankName : ranks | std::views::keys) { 34 | rankNames.push_back(rankName); 35 | } 36 | 37 | form.appendDropdown( 38 | "rankName", 39 | manager::LanguageManager::getTranslate("formSetRankDropdownRanks", player.getLocaleCode()), 40 | rankNames 41 | ); 42 | 43 | form.sendTo( 44 | player, 45 | [ranks = std::move( 46 | ranks 47 | )](Player& player, const ll::form::CustomFormResult& result, ll::form::FormCancelReason reason) -> void { 48 | if (reason.has_value()) { 49 | return; 50 | } 51 | 52 | std::string playerName; 53 | const types::Rank* rank; 54 | 55 | try { 56 | playerName = std::get_if(&result->at("playerName"))->data(); 57 | rank = ranks[std::get_if(&result->at("rankName"))->data()]; 58 | } catch (...) { 59 | player.sendMessage(manager::LanguageManager::getTranslate("undefinedError", player.getLocaleCode())); 60 | return; 61 | } 62 | 63 | if (manager::ConfigManager::getConfig().superPlayers.contains(playerName)) { 64 | player.sendMessage( 65 | Utils::strReplace( 66 | manager::LanguageManager::getTranslate("setRankSuperPlayer", player.getLocaleCode()), 67 | "{playerName}", 68 | playerName 69 | ) 70 | ); 71 | return; 72 | } 73 | 74 | if (manager::ConfigManager::getConfig().superRanks.contains(rank->getName())) { 75 | player.sendMessage(manager::LanguageManager::getTranslate("setRankSuperRank", player.getLocaleCode())); 76 | return; 77 | } 78 | 79 | if (Player* otherPlayer = player.getLevel().getPlayer(playerName); otherPlayer != nullptr) { 80 | manager::MainManager::setPlayerRank(*otherPlayer, *rank); 81 | } else { 82 | manager::MainManager::setPlayerRankByName(playerName, *rank); 83 | } 84 | 85 | player.sendMessage( 86 | Utils::strReplace( 87 | manager::LanguageManager::getTranslate("setRankSuccess", player.getLocaleCode()), 88 | {"{playerName}", "{rankName}"}, 89 | {playerName, rank->getName()} 90 | ) 91 | ); 92 | } 93 | ); 94 | } 95 | 96 | } // namespace power_ranks::forms -------------------------------------------------------------------------------- /src/mod/hooks/Hooks.cpp: -------------------------------------------------------------------------------- 1 | #include "Hooks.h" 2 | #include "../manager/MainManager.h" 3 | #include "../manager/command/CommandManager.h" 4 | #include "../manager/config/ConfigManager.h" 5 | #include "../manager/rankFormats/RankFormatsManager.h" 6 | #include "../utils/Utils.h" 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | namespace power_ranks::hooks { 17 | 18 | LL_TYPE_INSTANCE_HOOK( 19 | PlayerJoinHook, 20 | HookPriority::Normal, 21 | ServerNetworkHandler, 22 | &ServerNetworkHandler::$handle, 23 | void, 24 | const NetworkIdentifier& identifier, 25 | const SetLocalPlayerAsInitializedPacket& packet 26 | ) { 27 | if (ServerPlayer* player = thisFor()->_getServerPlayer(identifier, packet.mSenderSubId); player) { 28 | manager::MainManager::updatePlayerRank(*player); 29 | } 30 | 31 | origin(identifier, packet); 32 | } 33 | 34 | LL_TYPE_INSTANCE_HOOK( 35 | CommandRunHook, 36 | HookPriority::Normal, 37 | Command, 38 | &Command::run, 39 | void, 40 | const CommandOrigin& commandOrigin, 41 | CommandOutput& output 42 | ) { 43 | if (commandOrigin.getEntity() == nullptr || !commandOrigin.getEntity()->isPlayer()) { 44 | return origin(commandOrigin, output); 45 | } 46 | 47 | ServerPlayer& player = static_cast(*commandOrigin.getEntity()); 48 | 49 | if (!manager::CommandManager::isCommandAvailable(commandOrigin, mFlags, mPermissionLevel)) { 50 | const types::Rank& rank = manager::MainManager::getPlayerRankOrSetDefault(player); 51 | if (!rank.isCommandAvailable(getCommandName())) { 52 | output.addMessage( 53 | "commands.generic.unknown", 54 | {CommandOutputParameter({getCommandName()})}, 55 | CommandOutputMessageType::Error 56 | ); 57 | return sendTelemetry(commandOrigin, output); 58 | } 59 | } 60 | 61 | // TODO: implement CommandRunStats... 62 | 63 | execute(commandOrigin, output); 64 | return sendTelemetry(commandOrigin, output); 65 | } 66 | 67 | LL_TYPE_INSTANCE_HOOK( 68 | DisplayGameMessageHook, 69 | HookPriority::Normal, 70 | ServerNetworkHandler, 71 | &ServerNetworkHandler::_displayGameMessage, 72 | void, 73 | const Player& sender, 74 | ChatEvent& chatEvent 75 | ) { 76 | Player& player = const_cast(sender); 77 | 78 | const types::Rank& rank = manager::MainManager::getPlayerRankOrSetDefault(player); 79 | if (!manager::ConfigManager::getConfig().ranksWithColoredMessages.contains(rank.getName())) { 80 | chatEvent.mMessage = Utils::strTrim(Utils::clean(chatEvent.mMessage)); 81 | } 82 | 83 | if (chatEvent.mMessage->empty()) { 84 | return; 85 | } 86 | 87 | chatEvent.mMessage = 88 | manager::RankFormatsManager::getChatFormat(rank.getName(), sender.getRealName(), chatEvent.mMessage); 89 | return origin(sender, chatEvent); 90 | } 91 | 92 | LL_TYPE_STATIC_HOOK( 93 | TextPacketCreateChatHook, 94 | HookPriority::Normal, 95 | TextPacket, 96 | &TextPacket::createChat, 97 | TextPacket, 98 | [[maybe_unused]] const std::string& author, 99 | const std::string& message, 100 | std::optional filteredMessage, 101 | const std::string& xuid, 102 | const std::string& platformId 103 | ) { 104 | return origin("", message, filteredMessage, xuid, platformId); 105 | } 106 | 107 | void setupHooks() { 108 | PlayerJoinHook::hook(); 109 | CommandRunHook::hook(); 110 | 111 | DisplayGameMessageHook::hook(); 112 | TextPacketCreateChatHook::hook(); 113 | } 114 | 115 | } // namespace power_ranks::hooks -------------------------------------------------------------------------------- /src/mod/manager/ranks/RanksManager.cpp: -------------------------------------------------------------------------------- 1 | #include "RanksManager.h" 2 | #include "../command/CommandManager.h" 3 | #include "../rankFormats/RankFormatsManager.h" 4 | #include 5 | #include 6 | 7 | namespace power_ranks::manager { 8 | 9 | int RanksManager::currentPriority = 0; 10 | RanksManager::Config RanksManager::config; 11 | std::filesystem::path RanksManager::pathToConfig; 12 | std::unordered_map RanksManager::ranks = {}; 13 | 14 | bool RanksManager::init(ll::mod::NativeMod& mod) { 15 | pathToConfig = mod.getDataDir() / "ranks.json"; 16 | 17 | try { 18 | bool result = ll::config::loadConfig(config, pathToConfig); 19 | parseRanks(); 20 | 21 | return result; 22 | } catch (const std::exception& e) { 23 | mod.getLogger().error("Failed to load rank config: {}", e.what()); 24 | } 25 | 26 | try { 27 | bool result = ll::config::saveConfig(config, pathToConfig); 28 | parseRanks(); 29 | 30 | return result; 31 | } catch (const std::exception& e) { 32 | mod.getLogger().error("Failed to save rank config: {}", e.what()); 33 | return false; 34 | } 35 | } 36 | 37 | void RanksManager::dispose() { 38 | for (const auto& [name, rank] : ranks) { 39 | delete rank; 40 | } 41 | 42 | ranks.clear(); 43 | } 44 | 45 | nlohmann::ordered_map RanksManager::getOrderedRanks() { 46 | nlohmann::ordered_map result = {}; 47 | for (const auto& [name, rawRank] : config.ranks) { 48 | const auto& rank = getRank(name); 49 | if (rank.has_value()) { 50 | result[name] = *rank; 51 | } 52 | } 53 | 54 | return result; 55 | } 56 | 57 | std::unordered_map RanksManager::getRanks() { return ranks; } 58 | 59 | std::optional RanksManager::getRank(const std::string& name) { 60 | if (!ranks.contains(name)) { 61 | return std::nullopt; 62 | } 63 | 64 | return ranks[name]; 65 | } 66 | 67 | void RanksManager::addRank( 68 | const std::string& name, 69 | const std::string& prefix, 70 | const std::string& chatFormat, 71 | const std::string& scoreTagFormat, 72 | const std::optional& inheritanceRank 73 | ) { 74 | RankFormatsManager::setRankFormat(name, prefix, chatFormat, scoreTagFormat, "ALL"); 75 | 76 | config.ranks[name] = Rank{inheritanceRank.has_value() ? inheritanceRank.value()->getName() : "null"}; 77 | 78 | ranks[name] = new types::Rank(currentPriority++, name, inheritanceRank); 79 | ll::config::saveConfig(config, pathToConfig); 80 | 81 | CommandManager::addRankNameToSoftEnum(name); 82 | } 83 | 84 | void RanksManager::removeRank(const types::Rank& rank) { 85 | std::string rankName = rank.getName(); 86 | RankFormatsManager::removeRankFormat(rankName); 87 | 88 | config.ranks.erase(rankName); 89 | ll::config::saveConfig(config, pathToConfig); 90 | 91 | delete ranks[rankName]; 92 | ranks.erase(rankName); 93 | 94 | CommandManager::removeRankNameFromSoftEnum(rankName); 95 | } 96 | 97 | void RanksManager::saveChangesRank(const types::Rank& rank) { 98 | config.ranks[rank.getName()] = Rank{ 99 | rank.getInheritanceRank().has_value() ? rank.getInheritanceRank().value()->getName() : "null", 100 | rank.getAvailableCommands(), 101 | rank.getHiddenCommandOverloads().getData() 102 | }; 103 | ll::config::saveConfig(config, pathToConfig); 104 | } 105 | 106 | void RanksManager::parseRanks() { 107 | if (config.ranks.find(ConfigManager::getConfig().defaultRankName) == config.ranks.end()) { 108 | throw std::runtime_error("No default rank detected!"); 109 | } 110 | 111 | for (const auto& [name, rawRank] : config.ranks) { 112 | types::Rank* rank = new types::Rank( 113 | currentPriority++, 114 | name, 115 | std::nullopt, 116 | rawRank.availableCommands, 117 | types::HiddenCommandOverloads(rawRank.hiddenCommandOverloads) 118 | ); 119 | ranks[name] = rank; 120 | } 121 | 122 | for (const auto& [name, rawRank] : config.ranks) { 123 | if (rawRank.inheritanceRank != "null") { 124 | ranks[name]->setInheritanceRank(ranks[rawRank.inheritanceRank]); 125 | } 126 | } 127 | } 128 | 129 | } // namespace power_ranks::manager -------------------------------------------------------------------------------- /src/mod/utils/Utils.cpp: -------------------------------------------------------------------------------- 1 | #include "Utils.h" 2 | #include 3 | #include 4 | #include 5 | 6 | namespace power_ranks { 7 | 8 | std::string Utils::clean(const std::string& input, bool removeFormat) { 9 | std::string string = input; 10 | 11 | // Remove Unicode Private Use Area characters (U+E000 to U+F8FF) 12 | string = removePrivateUseUnicode(string); 13 | 14 | if (removeFormat) { 15 | static constexpr std::string_view MC_ESCAPE_CODE = "§"; 16 | 17 | std::regex formatCodeRegex(std::string(MC_ESCAPE_CODE) + "[0-9a-v]", std::regex::icase); 18 | string = std::regex_replace(string, formatCodeRegex, ""); 19 | 20 | std::regex loneEscapeRegex((std::string(MC_ESCAPE_CODE))); 21 | string = std::regex_replace(string, loneEscapeRegex, ""); 22 | } 23 | 24 | // Remove ANSI escape sequences like \x1b[31m 25 | std::regex ansiRegex(R"(\x1b[\(\)\[\]0-9;]*[Bm])", std::regex::icase); 26 | string = std::regex_replace(string, ansiRegex, ""); 27 | 28 | // Remove stray \x1b bytes 29 | string.erase(std::remove(string.begin(), string.end(), '\x1b'), string.end()); 30 | return string; 31 | } 32 | 33 | std::string 34 | Utils::strReplace(const std::string& originalStr, std::string_view whatNeedToReplace, std::string_view whatForReplace) { 35 | std::string result = originalStr; 36 | 37 | size_t pos = 0; 38 | while ((pos = result.find(whatNeedToReplace, pos)) != std::string::npos) { 39 | result.replace(pos, whatNeedToReplace.size(), whatForReplace); 40 | pos += whatForReplace.size(); 41 | } 42 | 43 | return result; 44 | } 45 | 46 | std::string Utils::strReplace( 47 | const std::string& originalStr, 48 | const std::vector& whatNeedToReplace, 49 | const std::vector& whatForReplace 50 | ) { 51 | std::string result = originalStr; 52 | if (whatNeedToReplace.size() != whatForReplace.size()) { 53 | throw std::invalid_argument("Vectors «whatNeedToReplace» and «whatForReplace» must have the same size!"); 54 | } 55 | 56 | for (size_t i = 0; i < whatNeedToReplace.size(); ++i) { 57 | const std::string& searchFor = whatNeedToReplace[i]; 58 | const std::string& replaceWith = whatForReplace[i]; 59 | 60 | result = strReplace(result, searchFor, replaceWith); 61 | } 62 | 63 | return result; 64 | } 65 | 66 | std::vector Utils::strSplit(std::string_view str, std::string_view separator) { 67 | std::vector strings; 68 | if (separator == "") { 69 | return strings; 70 | } 71 | 72 | size_t start = 0, end; 73 | while ((end = str.find(separator, start)) != std::string::npos) { 74 | strings.emplace_back(strTrim(str.substr(start, end - start))); 75 | start = end + separator.size(); 76 | } 77 | 78 | strings.emplace_back(strTrim(str.substr(start))); 79 | return strings; 80 | } 81 | 82 | std::string Utils::strTrim(std::string_view str) { 83 | // clang-format off 84 | auto trimmed = str 85 | | std::views::drop_while([](unsigned char ch) -> int { return std::isspace(ch); }) 86 | | std::views::reverse 87 | | std::views::drop_while([](unsigned char ch) -> int { return std::isspace(ch); }) 88 | | std::views::reverse; 89 | // clang-format on 90 | 91 | return std::string(trimmed.begin(), trimmed.end()); 92 | } 93 | 94 | std::string Utils::removePrivateUseUnicode(std::string_view input) { 95 | std::string output; 96 | size_t i = 0; 97 | 98 | while (i < input.size()) { 99 | unsigned char c = input[i]; 100 | uint32_t codepoint = 0; 101 | size_t len = 0; 102 | 103 | if ((c & 0x80) == 0) { 104 | codepoint = c; 105 | len = 1; 106 | } else if ((c & 0xE0) == 0xC0 && i + 1 < input.size()) { 107 | codepoint = ((c & 0x1F) << 6) | (input[i + 1] & 0x3F); 108 | len = 2; 109 | } else if ((c & 0xF0) == 0xE0 && i + 2 < input.size()) { 110 | codepoint = ((c & 0x0F) << 12) | ((input[i + 1] & 0x3F) << 6) | (input[i + 2] & 0x3F); 111 | len = 3; 112 | } else if ((c & 0xF8) == 0xF0 && i + 3 < input.size()) { 113 | codepoint = ((c & 0x07) << 18) | ((input[i + 1] & 0x3F) << 12) | ((input[i + 2] & 0x3F) << 6) 114 | | (input[i + 3] & 0x3F); 115 | len = 4; 116 | } else { 117 | // Invalid UTF-8 sequence 118 | ++i; 119 | continue; 120 | } 121 | 122 | if (codepoint < 0xE000 || codepoint > 0xF8FF) { 123 | output.append(input, i, len); 124 | } 125 | 126 | i += len; 127 | } 128 | 129 | return output; 130 | } 131 | 132 | } // namespace power_ranks -------------------------------------------------------------------------------- /src/mod/forms/AddRankForm.cpp: -------------------------------------------------------------------------------- 1 | #include "AddRankForm.h" 2 | #include "../manager/lang/LanguageManager.h" 3 | #include "../manager/rankFormats/RankFormatsManager.h" 4 | #include "../manager/ranks/RanksManager.h" 5 | #include "../utils/Utils.h" 6 | #include 7 | 8 | namespace power_ranks::forms { 9 | 10 | void AddRankForm::init(Player& player) { 11 | ll::form::CustomForm form(manager::LanguageManager::getTranslate("formAddRankTitle", player.getLocaleCode())); 12 | 13 | form.appendInput( 14 | "rankName", 15 | manager::LanguageManager::getTranslate("formAddRankInputRankName", player.getLocaleCode()), 16 | manager::LanguageManager::getTranslate("formAddRankInputRankNamePlaceholder", player.getLocaleCode()) 17 | ); 18 | form.appendInput( 19 | "prefix", 20 | manager::LanguageManager::getTranslate("formAddRankInputPrefix", player.getLocaleCode()), 21 | manager::LanguageManager::getTranslate("formAddRankInputPrefixPlaceholder", player.getLocaleCode()) 22 | ); 23 | form.appendInput( 24 | "chatFormat", 25 | manager::LanguageManager::getTranslate("formAddRankInputChatFormat", player.getLocaleCode()), 26 | manager::LanguageManager::getTranslate("formAddRankInputChatFormatPlaceholder", player.getLocaleCode()), 27 | "({prefix}) {playerName}: {message}" 28 | ); 29 | form.appendInput( 30 | "scoreTagFormat", 31 | manager::LanguageManager::getTranslate("formAddRankInputScoreTagFormat", player.getLocaleCode()), 32 | manager::LanguageManager::getTranslate("formAddRankInputScoreTagFormatPlaceholder", player.getLocaleCode()), 33 | "{prefix}" 34 | ); 35 | 36 | nlohmann::ordered_map> ranks = { 37 | {manager::LanguageManager::getTranslate("dropdownDontPoint", player.getLocaleCode()), std::nullopt} 38 | }; 39 | for (const auto& [name, rank] : manager::RanksManager::getOrderedRanks()) { 40 | ranks[std::format("{} - {}", name, manager::RankFormatsManager::getPrefixFormat(rank->getName()))] = rank; 41 | } 42 | 43 | std::vector rankNames = {}; 44 | for (const auto& rankName : ranks | std::views::keys) { 45 | rankNames.push_back(rankName); 46 | } 47 | 48 | form.appendDropdown( 49 | "inheritanceRankName", 50 | manager::LanguageManager::getTranslate("formAddRankDropdownRanks", player.getLocaleCode()), 51 | rankNames 52 | ); 53 | 54 | form.sendTo( 55 | player, 56 | [ranks = std::move( 57 | ranks 58 | )](Player& player, const ll::form::CustomFormResult& result, ll::form::FormCancelReason reason) -> void { 59 | if (reason.has_value()) { 60 | return; 61 | } 62 | 63 | std::string rankName; 64 | std::string prefix; 65 | std::string chatFormat; 66 | std::string scoreTagFormat; 67 | std::optional inheritanceRank; 68 | 69 | try { 70 | rankName = std::get_if(&result->at("rankName"))->data(); 71 | prefix = std::get_if(&result->at("prefix"))->data(); 72 | chatFormat = std::get_if(&result->at("chatFormat"))->data(); 73 | scoreTagFormat = std::get_if(&result->at("scoreTagFormat"))->data(); 74 | inheritanceRank = ranks[std::get_if(&result->at("inheritanceRankName"))->data()]; 75 | } catch (...) { 76 | player.sendMessage(manager::LanguageManager::getTranslate("undefinedError", player.getLocaleCode())); 77 | return; 78 | } 79 | 80 | if (rankName.empty() || prefix.empty() || chatFormat.empty()) { 81 | player.sendMessage(manager::LanguageManager::getTranslate("formIncorrectData", player.getLocaleCode())); 82 | return; 83 | } 84 | 85 | if (manager::RanksManager::getRank(rankName).has_value()) { 86 | player.sendMessage( 87 | manager::LanguageManager::getTranslate("addRankAlreadyExists", player.getLocaleCode()) 88 | ); 89 | return; 90 | } 91 | 92 | manager::RanksManager::addRank(rankName, prefix, chatFormat, scoreTagFormat, inheritanceRank); 93 | player.sendMessage( 94 | Utils::strReplace( 95 | manager::LanguageManager::getTranslate("addRankSuccess", player.getLocaleCode()), 96 | "{rankName}", 97 | rankName 98 | ) 99 | ); 100 | } 101 | ); 102 | } 103 | 104 | } // namespace power_ranks::forms -------------------------------------------------------------------------------- /.clang-tidy: -------------------------------------------------------------------------------- 1 | --- 2 | Checks: '-*, 3 | bugprone-argument-comment, 4 | bugprone-assert-side-effect, 5 | bugprone-bad-signal-to-kill-thread, 6 | bugprone-branch-clone, 7 | bugprone-copy-constructor-init, 8 | bugprone-dangling-handle, 9 | bugprone-dynamic-static-initializers, 10 | bugprone-fold-init-type, 11 | bugprone-forward-declaration-namespace, 12 | bugprone-forwarding-reference-overload, 13 | bugprone-inaccurate-erase, 14 | bugprone-incorrect-roundings, 15 | bugprone-integer-division, 16 | bugprone-lambda-function-name, 17 | bugprone-macro-parentheses, 18 | bugprone-macro-repeated-side-effects, 19 | bugprone-misplaced-operator-in-strlen-in-alloc, 20 | bugprone-misplaced-pointer-arithmetic-in-alloc, 21 | bugprone-misplaced-widening-cast, 22 | bugprone-move-forwarding-reference, 23 | bugprone-multiple-statement-macro, 24 | bugprone-no-escape, 25 | bugprone-not-null-terminated-result, 26 | bugprone-parent-virtual-call, 27 | bugprone-posix-return, 28 | bugprone-reserved-identifier, 29 | bugprone-sizeof-container, 30 | bugprone-sizeof-expression, 31 | bugprone-spuriously-wake-up-functions, 32 | bugprone-string-constructor, 33 | bugprone-string-integer-assignment, 34 | bugprone-string-literal-with-embedded-nul, 35 | bugprone-suspicious-enum-usage, 36 | bugprone-suspicious-include, 37 | bugprone-suspicious-memory-comparison, 38 | bugprone-suspicious-memset-usage, 39 | bugprone-suspicious-missing-comma, 40 | bugprone-suspicious-semicolon, 41 | bugprone-suspicious-string-compare, 42 | bugprone-swapped-arguments, 43 | bugprone-terminating-continue, 44 | bugprone-throw-keyword-missing, 45 | bugprone-too-small-loop-variable, 46 | bugprone-undefined-memory-manipulation, 47 | bugprone-undelegated-constructor, 48 | bugprone-unhandled-self-assignment, 49 | bugprone-unused-raii, 50 | bugprone-unused-return-value, 51 | bugprone-use-after-move, 52 | bugprone-virtual-near-miss, 53 | cert-dcl21-cpp, 54 | cert-dcl58-cpp, 55 | cert-err34-c, 56 | cert-err52-cpp, 57 | cert-err60-cpp, 58 | cert-flp30-c, 59 | cert-msc50-cpp, 60 | cert-msc51-cpp, 61 | cert-str34-c, 62 | cppcoreguidelines-interfaces-global-init, 63 | cppcoreguidelines-narrowing-conversions, 64 | cppcoreguidelines-pro-type-member-init, 65 | cppcoreguidelines-slicing, 66 | google-default-arguments, 67 | google-explicit-constructor, 68 | google-runtime-operator, 69 | hicpp-exception-baseclass, 70 | hicpp-multiway-paths-covered, 71 | misc-misplaced-const, 72 | misc-new-delete-overloads, 73 | misc-non-copyable-objects, 74 | misc-throw-by-value-catch-by-reference, 75 | misc-unconventional-assign-operator, 76 | misc-uniqueptr-reset-release, 77 | modernize-avoid-bind, 78 | modernize-concat-nested-namespaces, 79 | modernize-deprecated-headers, 80 | modernize-deprecated-ios-base-aliases, 81 | modernize-loop-convert, 82 | modernize-make-shared, 83 | modernize-make-unique, 84 | modernize-pass-by-value, 85 | modernize-raw-string-literal, 86 | modernize-redundant-void-arg, 87 | modernize-replace-auto-ptr, 88 | modernize-replace-disallow-copy-and-assign-macro, 89 | modernize-replace-random-shuffle, 90 | modernize-return-braced-init-list, 91 | modernize-shrink-to-fit, 92 | modernize-unary-static-assert, 93 | modernize-use-auto, 94 | modernize-use-bool-literals, 95 | modernize-use-emplace, 96 | modernize-use-equals-default, 97 | modernize-use-equals-delete, 98 | modernize-use-nodiscard, 99 | modernize-use-noexcept, 100 | modernize-use-nullptr, 101 | modernize-use-override, 102 | modernize-use-transparent-functors, 103 | modernize-use-uncaught-exceptions, 104 | mpi-buffer-deref, 105 | mpi-type-mismatch, 106 | openmp-use-default-none, 107 | performance-faster-string-find, 108 | performance-for-range-copy, 109 | performance-implicit-conversion-in-loop, 110 | performance-inefficient-algorithm, 111 | performance-inefficient-string-concatenation, 112 | performance-inefficient-vector-operation, 113 | performance-move-const-arg, 114 | performance-move-constructor-init, 115 | performance-no-automatic-move, 116 | performance-noexcept-move-constructor, 117 | performance-trivially-destructible, 118 | performance-type-promotion-in-math-fn, 119 | performance-unnecessary-copy-initialization, 120 | performance-unnecessary-value-param, 121 | portability-simd-intrinsics, 122 | readability-avoid-const-params-in-decls, 123 | readability-const-return-type, 124 | readability-container-size-empty, 125 | readability-delete-null-pointer, 126 | readability-deleted-default, 127 | readability-inconsistent-declaration-parameter-name, 128 | readability-make-member-function-const, 129 | readability-misleading-indentation, 130 | readability-misplaced-array-index, 131 | readability-non-const-parameter, 132 | readability-redundant-control-flow, 133 | readability-redundant-declaration, 134 | readability-redundant-function-ptr-dereference, 135 | readability-redundant-smartptr-get, 136 | readability-redundant-string-cstr, 137 | readability-redundant-string-init, 138 | readability-simplify-subscript-expr, 139 | readability-static-accessed-through-instance, 140 | readability-static-definition-in-anonymous-namespace, 141 | readability-string-compare, 142 | readability-uniqueptr-delete-release, 143 | readability-use-anyofallof' 144 | ... -------------------------------------------------------------------------------- /src/mod/commands/SetRankCommand.cpp: -------------------------------------------------------------------------------- 1 | #include "SetRankCommand.h" 2 | #include "../utils/Utils.h" 3 | #include "../forms/SetRankForm.h" 4 | #include "../manager/MainManager.h" 5 | #include "../manager/ranks/RanksManager.h" 6 | #include 7 | #include 8 | #include 9 | 10 | namespace power_ranks::commands { 11 | 12 | void SetRankCommand::execute( 13 | const CommandOrigin& origin, 14 | CommandOutput& output, 15 | const Parameter& parameter, 16 | [[maybe_unused]] const Command& _ 17 | ) { 18 | // clang-format off 19 | bool isOriginServer = origin.getEntity() == nullptr || !origin.getEntity()->isType(ActorType::Player); 20 | const auto& localeCode = isOriginServer ? manager::ConfigManager::getConfig().defaultLocaleCode : static_cast(*origin.getEntity()).getLocaleCode(); 21 | 22 | if (!isOriginServer && manager::ConfigManager::getConfig().superRanks.contains(parameter.rankName)) { 23 | // clang-format on 24 | output.error(manager::LanguageManager::getTranslate("setRankSuperRank", localeCode)); 25 | return; 26 | } 27 | 28 | const auto& rank = manager::RanksManager::getRank(parameter.rankName); 29 | if (!rank.has_value() || rank.value() == nullptr) { 30 | std::string ranks; 31 | for (const auto& [name, otherRank] : manager::RanksManager::getRanks()) { 32 | if (ranks.empty()) { 33 | ranks = name; 34 | continue; 35 | } 36 | 37 | ranks += ", " + name; 38 | } 39 | 40 | output.error(Utils::strReplace( 41 | manager::LanguageManager::getTranslate("undefinedRank", localeCode), 42 | {"{rankName}", "{ranks}"}, 43 | {parameter.rankName, std::move(ranks)} 44 | )); 45 | return; 46 | } 47 | 48 | CommandSelectorResults players = parameter.player.results(origin); 49 | if (players.empty()) { 50 | constexpr ptrdiff_t offset_mNameFilters = offsetof(CommandSelectorBase, mNameFilters); 51 | const std::vector>& filter = 52 | ll::memory::dAccess>>( 53 | reinterpret_cast(¶meter.player), 54 | offset_mNameFilters 55 | ); 56 | 57 | if (filter.empty() || filter.front().value == "") { 58 | output.error(manager::LanguageManager::getTranslate("commandSetRankUndefinedPlayer", localeCode)); 59 | return; 60 | } 61 | 62 | const auto& playerName = filter.front().value; 63 | manager::MainManager::setPlayerRankByName(playerName, *rank.value()); 64 | 65 | output.success(Utils::strReplace( 66 | manager::LanguageManager::getTranslate("setRankSuccess", localeCode), 67 | {"{playerName}", "{rankName}"}, 68 | {playerName, parameter.rankName} 69 | )); 70 | return; 71 | } 72 | 73 | std::vector playerNames; 74 | for (Player* player : *players.data) { 75 | // clang-format off 76 | if (!isOriginServer && manager::ConfigManager::getConfig().superPlayers.contains(player->getRealName())) { 77 | // clang-format on 78 | output.error(Utils::strReplace( 79 | manager::LanguageManager::getTranslate("setRankSuperPlayer", localeCode), 80 | "{playerName}", 81 | player->getRealName() 82 | )); 83 | continue; 84 | } 85 | 86 | manager::MainManager::setPlayerRank(*player, *rank.value()); 87 | playerNames.push_back(player->getRealName()); 88 | } 89 | 90 | if (playerNames.empty()) { 91 | if (!players.empty()) { 92 | return; 93 | } 94 | 95 | output.error(manager::LanguageManager::getTranslate("undefinedError", localeCode)); 96 | return; 97 | } 98 | 99 | if (playerNames.size() == 1) { 100 | output.success(Utils::strReplace( 101 | manager::LanguageManager::getTranslate("setRankSuccess", localeCode), 102 | {"{playerName}", "{rankName}"}, 103 | {playerNames[0], parameter.rankName} 104 | )); 105 | return; 106 | } 107 | 108 | std::string playerNamesStr; 109 | for (std::string& playerName : playerNames) { 110 | if (playerNamesStr.empty()) { 111 | playerNamesStr = playerName; 112 | continue; 113 | } 114 | 115 | playerNamesStr += ", " + playerName; 116 | } 117 | 118 | output.success(Utils::strReplace( 119 | manager::LanguageManager::getTranslate("commandSetRankSuccessMultiply", localeCode), 120 | {"{playerNames}", "{rankName}"}, 121 | {playerNamesStr, parameter.rankName} 122 | )); 123 | } 124 | 125 | void SetRankCommand::executeWithoutParameter(const CommandOrigin& origin, CommandOutput& output) { 126 | if (origin.getEntity() == nullptr || !origin.getEntity()->isType(ActorType::Player)) { 127 | output.error(manager::LanguageManager::getTranslate("commandSetRankUsing")); 128 | return; 129 | } 130 | 131 | forms::SetRankForm::init(static_cast(*origin.getEntity())); 132 | } 133 | 134 | } // namespace power_ranks::commands -------------------------------------------------------------------------------- /assets/data/languages/en_US.json: -------------------------------------------------------------------------------- 1 | { 2 | "languageName": "English (United Kingdom)", 3 | "undefinedError": "§r(Ranks) Sorry, an unknown error has occurred..", 4 | "undefinedRank": "§r(Ranks) Unknown rank {rankName}! Available ranks: {ranks}.", 5 | "dropdownDontPoint": "Don't point", 6 | "formIncorrectData": "§r(Ranks) Please enter the correct information in the fields.", 7 | "commandAddRankDescription": "Create a new rank.", 8 | "commandSetRankDescription": "Set a new rank for a player.", 9 | "commandRemoveRankDescription": "Remove rank.", 10 | "commandEditRankDescription": "Edit rank.", 11 | "commandAddRankUsing": "§r(Ranks) Usage: /addrank [inheritanceRank: string]", 12 | "addRankAlreadyExists": "§r(Ranks) A rank with this name already exists!", 13 | "addRankSuccess": "§r(Ranks) You have successfully created a rank called {rankName}!", 14 | "commandSetRankUsing": "§r(Ranks) Usage: /setrank ", 15 | "commandSetRankUndefinedPlayer": "§r(Ranks) Failed to get player!", 16 | "setRankSuperRank": "§r(Ranks) This rank can only be set from console!", 17 | "setRankSuperPlayer": "§r(Ranks) Player {playerName} can only be set to rank from console!", 18 | "setRankSuccess": "§r(Ranks) You have successfully set player {playerName} to rank {rankName}!", 19 | "commandSetRankSuccessMultiply": "§r(Ranks) You have successfully set the rank of {playerNames} to {rankName}!", 20 | "commandRemoveRankUsing": "§r(Ranks) Usage: /removerank ", 21 | "commandRemoveRankSuccess": "§r(Ranks) You have successfully removed the rank of {rankName}.", 22 | "commandEditRankUsing": "§r(Ranks) Usage: /editrank ", 23 | "editRankSuperRank": "§r(Ranks) This rank can only be edited from the console!", 24 | "editRankFirstInvalidLocaleCode": "§r(Ranks) This language code is unknown! Type \"ALL\" (without quotes) to replace the formatting in all languages.", 25 | "editRankSecondInvalidLocaleCode": "§r(Ranks) This language code is unknown! Default language code: \"{defaultLocaleCode}\".", 26 | "editRankInvalidFormatAvailableCommands": "§r(Ranks) Please enter available commands this rank separated by a semicolon (\";\", without quotes; example of the correct format: teleport;gamemode).", 27 | "editRankSuccess": "§r(Ranks) You have successfully edited the rank {rankName}.", 28 | "formAddRankTitle": "Creating a new rank", 29 | "formAddRankInputRankName": "Enter the name of the future rank:", 30 | "formAddRankInputRankNamePlaceholder": "Name of the future rank", 31 | "formAddRankInputPrefix": "Enter the prefix of the future rank:", 32 | "formAddRankInputPrefixPlaceholder": "Prefix of the future rank", 33 | "formAddRankInputChatFormat": "Enter the format for displaying messages of the player who will have this rank (the following variables are supported: {prefix} - rank prefix, {playerName} - player name, {message} - message sent by the player):", 34 | "formAddRankInputChatFormatPlaceholder": "Player message display format", 35 | "formAddRankInputScoreTagFormat": "Enter the player's scoreTag format (the {prefix} variable is supported - rank prefix):", 36 | "formAddRankInputScoreTagFormatPlaceholder": "Player scoreTag format", 37 | "formAddRankDropdownRanks": "Select a rank from the list from which the future rank will be inherited:", 38 | "formSetRankTitle": "Setting a new rank for a player", 39 | "formSetRankDropdownPlayers": "Select a player from the list to whom you want to set a new rank:", 40 | "formSetRankDropdownRanks": "Select a rank from the list that you want to set for the player:", 41 | "formEditRankTitle": "Editing the rank {rankName}", 42 | "formEditRankLabel": "The following three parameters (prefix, player message display format, player scoreTag) will be changed only for the language code you have selected (\"{localeCode}\"). The rest of the parameters will be changed GLOBALLY!", 43 | "formEditRankInputPrefix": "Enter the new prefix of the selected rank:", 44 | "formEditRankInputPrefixPlaceholder": "New rank prefix", 45 | "formEditRankInputChatFormat": "Enter the new format for displaying player messages that will have the selected rank (supported variables {prefix} - rank prefix, {playerName} - player name, {message} - message sent by the player):", 46 | "formEditRankInputChatFormatPlaceholder": "New format for displaying player messages", 47 | "formEditRankInputScoreTagFormat": "Enter the new format for the player's scoreTag (supported variable {prefix} - rank prefix):", 48 | "formEditRankInputScoreTagFormatPlaceholder": "New format for the player's scoreTag", 49 | "formEditRankDropdownInheritanceRanks": "Select a rank from the list, from which the selected rank will inherit:", 50 | "formEditRankInputAvailableCommands": "Enter new commands for the selected rank separated by semicolons (\";\", without quotes; enter the value \"null\" (without quotes) if you want to remove commands specifically available to this rank; example of the correct format: teleport;gamemode):", 51 | "formEditRankInputAvailableCommandsPlaceholder": "New rank commands", 52 | "formEditRankInputHiddenCommandOverloads": "Enter new command overloads that will be hidden for the selected rank with semicolon-separated indexes (\";\", without quotes; leave the field empty if you want to remove command overloads that will be hidden for the selected rank; example of the correct format: teleport:0,2,3;gamemode:1):", 53 | "formEditRankInputHiddenCommandOverloadsPlaceholder": "New command overloads that will be hidden", 54 | "formEditRankInputAdditionalInformation": "Enter additional information for the selected rank separated by a semicolon (\";\", without quotes; enter the value \"null\" (without quotes) if you want to remove additional information for the selected rank; this information can be used by other mods, for example, to maintain a uniform color style):", 55 | "formEditRankInputAdditionalInformationPlaceholder": "New additional information" 56 | } -------------------------------------------------------------------------------- /assets/data/languages/ru_RU.json: -------------------------------------------------------------------------------- 1 | { 2 | "languageName": "Русский (Россия)", 3 | "undefinedError": "§r(Ранги) К сожалению, произошла неизвестная ошибка..", 4 | "undefinedRank": "§r(Ранги) Неизвестный ранг {rankName}! Доступные ранги: {ranks}.", 5 | "dropdownDontPoint": "Не указывать", 6 | "formIncorrectData": "§r(Ранги) Пожалуйста, введите корректные данные в поля.", 7 | "commandAddRankDescription": "Создать новый ранг.", 8 | "commandSetRankDescription": "Установить игроку новый ранг.", 9 | "commandRemoveRankDescription": "Удалить ранг.", 10 | "commandEditRankDescription": "Отредактировать ранг.", 11 | "commandAddRankUsing": "§r(Ранги) Использование: /addrank [inheritanceRank: string]", 12 | "addRankAlreadyExists": "§r(Ранги) Ранг с таким названием уже существует!", 13 | "addRankSuccess": "§r(Ранги) Вы успешно создали ранг под названием {rankName}!", 14 | "commandSetRankUsing": "§r(Ранги) Использование: /setrank ", 15 | "commandSetRankUndefinedPlayer": "§r(Ранги) Не удалось получить игрока!", 16 | "setRankSuperRank": "§r(Ранги) Данный ранг можно устанавливать только с консоли!", 17 | "setRankSuperPlayer": "§r(Ранги) Игроку {playerName} можно устанавливать ранг только с консоли!", 18 | "setRankSuccess": "§r(Ранги) Вы успешно установили игроку {playerName} ранг {rankName}!", 19 | "commandSetRankSuccessMultiply": "§r(Ранги) Вы успешно установили игрокам {playerNames} ранг {rankName}!", 20 | "commandRemoveRankUsing": "§r(Ранги) Использование: /removerank ", 21 | "commandRemoveRankSuccess": "§r(Ранги) Вы успешно удалили ранг {rankName}.", 22 | "commandEditRankUsing": "§r(Ранги) Использование: /editrank ", 23 | "editRankSuperRank": "§r(Ранги) Данный ранг можно редактировать только с консоли!", 24 | "editRankFirstInvalidLocaleCode": "§r(Ранги) Данный языковой код неизвестен! Введите «ALL» (без кавычек), чтобы заменить форматирование на всех языках.", 25 | "editRankSecondInvalidLocaleCode": "§r(Ранги) Данный языковой код неизвестен! Языковой код по умолчанию: «{defaultLocaleCode}».", 26 | "editRankInvalidFormatAvailableCommands": "§r(Ранги) Пожалуйста, вводите доступные команды этому рангу через точку с запятой («;», без кавычек).", 27 | "editRankInvalidFormatAdditionalInformation": "§r(Ранги) Пожалуйста, вводите дополнительную информацию для этого ранга через точку с запятой («;», без кавычек).", 28 | "editRankSuccess": "§r(Ранги) Вы успешно отредактировали ранг {rankName}.", 29 | "formAddRankTitle": "Создание нового ранга", 30 | "formAddRankInputRankName": "Введите название будущего ранга:", 31 | "formAddRankInputRankNamePlaceholder": "Название будущего ранга", 32 | "formAddRankInputPrefix": "Введите префикс будущего ранга:", 33 | "formAddRankInputPrefixPlaceholder": "Префикс будущего ранга", 34 | "formAddRankInputChatFormat": "Введите формат отображения сообщений игрока, который будет иметь этот ранг (поддерживаются переменные {prefix} - префикс ранга, {playerName} - имя игрока, {message} - сообщение, отправленное игроком):", 35 | "formAddRankInputChatFormatPlaceholder": "Формат отображения сообщений игрока", 36 | "formAddRankInputScoreTagFormat": "Введите формат scoreTag'а игрока (поддерживается переменная {prefix} - префикс ранга):", 37 | "formAddRankInputScoreTagFormatPlaceholder": "Формат scoreTag'а игрока", 38 | "formAddRankDropdownRanks": "Выберите ранг из списка, от которого будет наследоваться будущий ранг:", 39 | "formSetRankTitle": "Установка игроку нового ранга", 40 | "formSetRankDropdownPlayers": "Выберите игрока из списка, которому хотите установить новый ранг:", 41 | "formSetRankDropdownRanks": "Выберите ранг из списка, который хотите установить игроку:", 42 | "formEditRankTitle": "Редактирование ранга {rankName}", 43 | "formEditRankLabel": "Следующие три параметра (префикс, формат отображения сообщений игрока, scoreTag игрока) будут изменены только для выбранного Вами языкового кода («{localeCode}»). Остальные же параметры будут изменены ГЛОБАЛЬНО!", 44 | "formEditRankInputPrefix": "Введите новый префикс выбранного ранга:", 45 | "formEditRankInputPrefixPlaceholder": "Новый префикс ранга", 46 | "formEditRankInputChatFormat": "Введите новый формат отображения сообщений игрока, который будет иметь выбранный ранг (поддерживаются переменные {prefix} - префикс ранга, {playerName} - имя игрока, {message} - сообщение, отправленное игроком):", 47 | "formEditRankInputChatFormatPlaceholder": "Новый формат отображения сообщений игрока", 48 | "formEditRankInputScoreTagFormat": "Введите новый формат scoreTag'а игрока (поддерживается переменная {prefix} - префикс ранга):", 49 | "formEditRankInputScoreTagFormatPlaceholder": "Новый формат scoreTag'а игрока", 50 | "formEditRankDropdownInheritanceRanks": "Выберите ранг из списка, от которого будет наследоваться выбранный ранг:", 51 | "formEditRankInputAvailableCommands": "Введите новые команды для выбранного ранга через точку с запятой («;», без кавычек; введите значение «null» (без кавычек), если хотите убрать команды, специально доступные этому рангу):", 52 | "formEditRankInputAvailableCommandsPlaceholder": "Новые команды ранга", 53 | "formEditRankInputHiddenCommandOverloads": "Введите новые перегрузки команд, которые будут скрыты для выбранного ранга, с индексами через точку с запятой («;», без кавычек; оставьте поле пустым, если хотите убрать перегрузки команд, которые будут скрыты для выбранного ранга; пример правильного формата: teleport:0,2,3;gamemode:1):", 54 | "formEditRankInputHiddenCommandOverloadsPlaceholder": "Новые перегрузки команд, которые будут скрыты", 55 | "formEditRankInputAdditionalInformation": "Введите дополнительную информацию для выбранного ранга через точку с запятой («;», без кавычек; введите значение «null» (без кавычек), если хотите убрать дополнительную информацию для выбранного ранга; данная информация может использоваться другими модами, например, для поддержания единого цветового стиля):", 56 | "formEditRankInputAdditionalInformationPlaceholder": "Новая дополнительная информация" 57 | } -------------------------------------------------------------------------------- /src/mod/manager/command/CommandManager.cpp: -------------------------------------------------------------------------------- 1 | #include "CommandManager.h" 2 | #include "../../commands/AddRankCommand.h" 3 | #include "../../commands/EditRankCommand.h" 4 | #include "../../commands/RemoveRankCommand.h" 5 | #include "../../commands/SetRankCommand.h" 6 | #include "../../utils/Utils.h" 7 | #include "../ranks/RanksManager.h" 8 | #include 9 | #include 10 | #include 11 | 12 | namespace power_ranks::manager { 13 | 14 | bool CommandManager::registerCommands() { 15 | optional_ref commandRegistry = ll::service::getCommandRegistry(); 16 | if (!commandRegistry) { 17 | return false; 18 | } 19 | 20 | std::vector rankNames = {}; 21 | for (const auto& [name, rank] : RanksManager::getRanks()) { 22 | rankNames.push_back(name); 23 | } 24 | ll::command::CommandRegistrar::getInstance().tryRegisterSoftEnum(std::string{rankEnumNames}, rankNames); 25 | 26 | ll::command::CommandHandle& addRankCommand = ll::command::CommandRegistrar::getInstance().getOrCreateCommand( 27 | commands::AddRankCommand::getName(), 28 | commands::AddRankCommand::getDescription(), 29 | commands::AddRankCommand::getRequirement(), 30 | commands::AddRankCommand::getFlag() 31 | ); 32 | 33 | for (const std::string& alias : commands::AddRankCommand::getAliases()) { 34 | addRankCommand.alias(alias); 35 | } 36 | 37 | addRankCommand.overload() 38 | .required("rankName") 39 | .required("prefix") 40 | .required("chat") 41 | .required("scoreTag") 42 | .optional("inheritanceRank") 43 | .execute(&commands::AddRankCommand::execute); 44 | 45 | addRankCommand.overload().execute(&commands::AddRankCommand::executeWithoutParameter); 46 | 47 | ll::command::CommandHandle& setRankCommand = ll::command::CommandRegistrar::getInstance().getOrCreateCommand( 48 | commands::SetRankCommand::getName(), 49 | commands::SetRankCommand::getDescription(), 50 | commands::SetRankCommand::getRequirement(), 51 | commands::SetRankCommand::getFlag() 52 | ); 53 | 54 | for (const std::string& alias : commands::SetRankCommand::getAliases()) { 55 | setRankCommand.alias(alias); 56 | } 57 | 58 | setRankCommand.overload() 59 | .required("rankName") 60 | .optional("player") 61 | .execute(&commands::SetRankCommand::execute); 62 | 63 | setRankCommand.overload().execute(&commands::SetRankCommand::executeWithoutParameter); 64 | 65 | ll::command::CommandHandle& removeRankCommand = ll::command::CommandRegistrar::getInstance().getOrCreateCommand( 66 | commands::RemoveRankCommand::getName(), 67 | commands::RemoveRankCommand::getDescription(), 68 | commands::RemoveRankCommand::getRequirement(), 69 | commands::RemoveRankCommand::getFlag() 70 | ); 71 | 72 | for (const std::string& alias : commands::RemoveRankCommand::getAliases()) { 73 | removeRankCommand.alias(alias); 74 | } 75 | 76 | removeRankCommand.overload() 77 | .required("rankName") 78 | .execute(&commands::RemoveRankCommand::execute); 79 | 80 | removeRankCommand.overload().execute(&commands::RemoveRankCommand::executeWithoutParameter); 81 | 82 | ll::command::CommandHandle& editRankCommand = ll::command::CommandRegistrar::getInstance().getOrCreateCommand( 83 | commands::EditRankCommand::getName(), 84 | commands::EditRankCommand::getDescription(), 85 | commands::EditRankCommand::getRequirement(), 86 | commands::EditRankCommand::getFlag() 87 | ); 88 | 89 | for (const std::string& alias : commands::EditRankCommand::getAliases()) { 90 | editRankCommand.alias(alias); 91 | } 92 | 93 | editRankCommand.overload() 94 | .required("rankName") 95 | .required("localeCode") 96 | .execute(&commands::EditRankCommand::executeFirstParameter); 97 | 98 | editRankCommand.overload() 99 | .required("rankName") 100 | .required("prefix") 101 | .required("chat") 102 | .required("scoreTag") 103 | .required("inheritanceRank") 104 | .required("availableCommands") 105 | .required("hiddenCommandOverloads") 106 | .required("additionalInformation") 107 | .execute(&commands::EditRankCommand::executeSecondParameter); 108 | 109 | editRankCommand.overload().execute(&commands::EditRankCommand::executeWithoutParameter); 110 | return true; 111 | } 112 | 113 | void CommandManager::addRankNameToSoftEnum(const std::string& rankName) { 114 | ll::command::CommandRegistrar::getInstance().addSoftEnumValues(std::string{rankEnumNames}, {rankName}); 115 | } 116 | 117 | void CommandManager::removeRankNameFromSoftEnum(const std::string& rankName) { 118 | ll::command::CommandRegistrar::getInstance().removeSoftEnumValues(std::string{rankEnumNames}, {rankName}); 119 | } 120 | 121 | bool CommandManager::isCommandAvailable( 122 | const CommandOrigin& origin, 123 | CommandFlag flags, 124 | CommandPermissionLevel permissionLevel 125 | ) { 126 | if (origin.getPermissionsLevel() < permissionLevel) { 127 | return false; 128 | } 129 | 130 | const CommandOrigin& outputReceiver = origin.getOutputReceiver(); 131 | unsigned int originType = static_cast(outputReceiver.getOriginType()); 132 | 133 | bool firstCheck = false; 134 | bool secondCheck = false; 135 | 136 | if (originType || (flags.value & CommandFlagValue::HiddenFromPlayerOrigin) == CommandFlagValue::None) { 137 | if ((originType - 1) <= 1u 138 | && (flags.value & CommandFlagValue::HiddenFromCommandBlockOrigin) != CommandFlagValue::None) { 139 | secondCheck = true; 140 | } 141 | } else { 142 | firstCheck = true; 143 | } 144 | 145 | bool result = (originType - 5) <= 1u 146 | && (flags.value & CommandFlagValue::HiddenFromAutomationOrigin) != CommandFlagValue::None; 147 | if (!firstCheck && !secondCheck) { 148 | return !result; 149 | } 150 | 151 | return false; 152 | } 153 | 154 | } // namespace power_ranks::manager -------------------------------------------------------------------------------- /src/mod/manager/rankFormats/RankFormatsManager.cpp: -------------------------------------------------------------------------------- 1 | #include "RankFormatsManager.h" 2 | #include "../../utils/Utils.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace power_ranks::manager { 9 | 10 | std::unordered_map RankFormatsManager::configs = {}; 11 | 12 | bool RankFormatsManager::init(ll::mod::NativeMod& mod) { 13 | bool result = false; 14 | 15 | std::filesystem::path pathToConfigs = mod.getDataDir() / "rankFormats"; 16 | for (const std::filesystem::directory_entry& entry : std::filesystem::directory_iterator(pathToConfigs)) { 17 | if (!entry.is_regular_file() || entry.path().extension() != ".json") { 18 | continue; 19 | } 20 | 21 | RankFormatsManager::Config config; 22 | 23 | try { 24 | result = ll::config::loadConfig(config, entry.path()); 25 | } catch (const std::exception& e) { 26 | mod.getLogger().error("Failed to load rank format config from {}: {}", entry.path(), e.what()); 27 | return false; 28 | } 29 | 30 | configs[entry.path().stem().string()] = {entry.path(), std::move(config)}; 31 | } 32 | 33 | if (!configs.contains(ConfigManager::getConfig().defaultLocaleCode)) { 34 | mod.getLogger().error("Failed to find default locale config: {}", ConfigManager::getConfig().defaultLocaleCode); 35 | return false; 36 | } 37 | 38 | generatePlaceholders(); 39 | return result; 40 | } 41 | 42 | bool RankFormatsManager::isKnownLocaleCode(std::string_view localeCode, bool supportAll) { 43 | return (localeCode == "ALL" && supportAll) || configs.find(std::string(localeCode)) != configs.end(); 44 | } 45 | 46 | std::string RankFormatsManager::getRawPrefixFormat(const std::string& rankName, const std::string& localeCode) { 47 | const RankFormatsManager::ConfigInfo& configInfo = getConfig(localeCode); 48 | return configInfo.config.ranks[rankName].prefix; 49 | } 50 | 51 | std::string RankFormatsManager::getRawChatFormat(const std::string& rankName, const std::string& localeCode) { 52 | const RankFormatsManager::ConfigInfo& configInfo = getConfig(localeCode); 53 | return configInfo.config.ranks[rankName].chat; 54 | } 55 | 56 | std::string RankFormatsManager::getRawScoreTagFormat(const std::string& rankName, const std::string& localeCode) { 57 | const RankFormatsManager::ConfigInfo& configInfo = getConfig(localeCode); 58 | return configInfo.config.ranks[rankName].scoreTag; 59 | } 60 | 61 | std::string RankFormatsManager::getPrefixFormat(const std::string& rankName) { 62 | return translator::api::generatePlaceholder(std::format("PowerRanks_{}_prefix", rankName)); 63 | } 64 | 65 | std::string RankFormatsManager::getScoreTagFormat(const std::string& rankName) { 66 | return translator::api::generatePlaceholder(std::format("PowerRanks_{}_scoreTag", rankName)); 67 | } 68 | 69 | std::string RankFormatsManager::getChatFormat( 70 | const std::string& rankName, 71 | const std::string& playerName, 72 | const std::string& message 73 | ) { 74 | const auto& prefixFormat = getPrefixFormat(rankName); 75 | const auto& temporaryPlaceholder = translator::api::generateTemporaryPlaceholder(); 76 | 77 | for (const auto& [localeCode, configInfo] : configs) { 78 | const auto& rankFormat = configInfo.config.ranks[rankName]; 79 | const auto& chat = Utils::strReplace( 80 | rankFormat.chat, 81 | {"{prefix}", "{playerName}", "{message}"}, 82 | {prefixFormat, playerName, message} 83 | ); 84 | 85 | translator::api::setTemporaryPlaceholder(temporaryPlaceholder, chat, localeCode); 86 | } 87 | 88 | return temporaryPlaceholder; 89 | } 90 | 91 | void RankFormatsManager::setRankFormat( 92 | const std::string& rankName, 93 | const std::string& prefix, 94 | const std::string& chat, 95 | const std::string& scoreTag, 96 | std::string_view localeCode 97 | ) { 98 | const auto& prefixPlaceholder = getPrefixFormat(rankName); 99 | const auto& scoreTagPlaceholder = getScoreTagFormat(rankName); 100 | 101 | if (localeCode == "ALL") { 102 | for (auto& [configLocaleCode, configInfo] : configs) { 103 | configInfo.config.ranks[rankName].prefix = prefix; 104 | configInfo.config.ranks[rankName].chat = chat; 105 | configInfo.config.ranks[rankName].scoreTag = scoreTag; 106 | 107 | ll::config::saveConfig(configInfo.config, configInfo.pathToConfig); 108 | 109 | translator::api::setPlaceholder(prefixPlaceholder, prefix, configLocaleCode); 110 | translator::api::setPlaceholder( 111 | scoreTagPlaceholder, 112 | Utils::strReplace(scoreTag, "{prefix}", prefix), 113 | configLocaleCode 114 | ); 115 | } 116 | return; 117 | } 118 | 119 | RankFormatsManager::ConfigInfo& configInfo = getConfig(localeCode); 120 | 121 | configInfo.config.ranks[rankName].prefix = prefix; 122 | configInfo.config.ranks[rankName].chat = chat; 123 | configInfo.config.ranks[rankName].scoreTag = scoreTag; 124 | 125 | ll::config::saveConfig(configInfo.config, configInfo.pathToConfig); 126 | 127 | translator::api::setPlaceholder(prefixPlaceholder, prefix, std::string(localeCode)); 128 | translator::api::setPlaceholder( 129 | scoreTagPlaceholder, 130 | Utils::strReplace(scoreTag, "{prefix}", prefix), 131 | std::string(localeCode) 132 | ); 133 | } 134 | 135 | void RankFormatsManager::removeRankFormat(const std::string& rankName) { 136 | for (auto& [localeCode, configInfo] : configs) { 137 | configInfo.config.ranks.erase(rankName); 138 | ll::config::saveConfig(configInfo.config, configInfo.pathToConfig); 139 | } 140 | } 141 | 142 | void RankFormatsManager::generatePlaceholders() { 143 | for (const auto& [localeCode, configInfo] : configs) { 144 | for (const auto& [rankName, rankFormat] : configInfo.config.ranks) { 145 | const auto& prefixPlaceholder = getPrefixFormat(rankName); 146 | const auto& scoreTagPlaceholder = getScoreTagFormat(rankName); 147 | 148 | translator::api::setPlaceholder(prefixPlaceholder, rankFormat.prefix, localeCode); 149 | translator::api::setPlaceholder( 150 | scoreTagPlaceholder, 151 | Utils::strReplace(rankFormat.scoreTag, "{prefix}", rankFormat.prefix), 152 | localeCode 153 | ); 154 | } 155 | } 156 | } 157 | 158 | RankFormatsManager::ConfigInfo& RankFormatsManager::getConfig(std::string_view localeCode) { 159 | auto it = configs.find(std::string(localeCode)); 160 | if (it != configs.end()) { 161 | return it->second; 162 | } 163 | 164 | return configs.at(ConfigManager::getConfig().defaultLocaleCode); 165 | } 166 | 167 | } // namespace power_ranks::manager -------------------------------------------------------------------------------- /src/mod/manager/MainManager.cpp: -------------------------------------------------------------------------------- 1 | #include "MainManager.h" 2 | #include "../utils/Utils.h" 3 | #include "base/BaseManager.h" 4 | #include "config/ConfigManager.h" 5 | #include "lang/LanguageManager.h" 6 | #include "rankFormats/RankFormatsManager.h" 7 | #include "ranks/RanksManager.h" 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | // wth mojang? 19 | AvailableCommandsPacket::CommandData::CommandData(const CommandData&) = default; 20 | 21 | namespace power_ranks::manager { 22 | 23 | bool MainManager::initManagers(ll::mod::NativeMod& mod) { 24 | BaseManager::init(mod); 25 | 26 | bool configInit = ConfigManager::init(mod); 27 | if (!configInit) { 28 | mod.getLogger().error("Failed to init ConfigManager!"); 29 | return false; 30 | } 31 | 32 | LanguageManager::init(mod); 33 | LanguageManager::addTranslations(); 34 | 35 | if (!RankFormatsManager::init(mod)) { 36 | mod.getLogger().error("Failed to init RankFormatsManager!"); 37 | return false; 38 | } 39 | 40 | if (!RanksManager::init(mod)) { 41 | mod.getLogger().error("Failed to init RanksManager!"); 42 | return false; 43 | } 44 | 45 | return true; 46 | } 47 | 48 | void MainManager::disposeManagers() { RanksManager::dispose(); } 49 | 50 | const types::Rank& MainManager::getPlayerRankOrSetDefault(Player& player) { 51 | const auto& entry = player_db::api::getPlayerEntry(player); 52 | 53 | if (const auto& rankName = BaseManager::getPlayerRank(entry.uuid); rankName.has_value()) { 54 | if (const auto& rank = RanksManager::getRank(rankName.value()); rank.has_value()) { 55 | return **rank; 56 | } 57 | } 58 | 59 | const auto& rank = **RanksManager::getRank(ConfigManager::getConfig().defaultRankName); 60 | 61 | setPlayerRank(player, rank); 62 | return rank; 63 | } 64 | 65 | const types::Rank& MainManager::getPlayerRankOrSetDefault(const std::string& playerName) { 66 | auto entry = player_db::api::getPlayerEntryByName(playerName); 67 | if (!entry.has_value()) { 68 | entry = player_db::api::addUnknownPlayerEntry(playerName); 69 | } 70 | 71 | if (const auto& rankName = BaseManager::getPlayerRank(entry->uuid); rankName.has_value()) { 72 | if (const auto& rank = RanksManager::getRank(rankName.value()); rank.has_value()) { 73 | return **rank; 74 | } 75 | } 76 | 77 | BaseManager::setPlayerRank(entry->uuid, ConfigManager::getConfig().defaultRankName); 78 | return *RanksManager::getRank(ConfigManager::getConfig().defaultRankName).value(); 79 | } 80 | 81 | void MainManager::setPlayerRank(Player& player, const types::Rank& rank) { 82 | const auto& entry = player_db::api::getPlayerEntry(player); 83 | 84 | BaseManager::setPlayerRank(entry.uuid, rank.getName()); 85 | updatePlayerRank(player); 86 | } 87 | 88 | void MainManager::setPlayerRankByName(const std::string& playerName, const types::Rank& rank) { 89 | auto entry = player_db::api::getPlayerEntryByName(playerName); 90 | if (!entry.has_value()) { 91 | entry = player_db::api::addUnknownPlayerEntry(playerName); 92 | } 93 | 94 | BaseManager::setPlayerRank(entry->uuid, rank.getName()); 95 | if (Player* player = ll::service::getLevel()->getPlayer(playerName); player != nullptr) { 96 | updatePlayerRank(*player); 97 | } 98 | } 99 | 100 | void MainManager::setPlayerRankByXuid(const std::string& xuid, const types::Rank& rank) { 101 | auto entry = player_db::api::getPlayerEntryByXuid(xuid); 102 | if (!entry.has_value()) { 103 | entry = player_db::api::addUnknownPlayerEntry("", xuid); 104 | } 105 | 106 | BaseManager::setPlayerRank(entry->uuid, rank.getName()); 107 | if (Player* player = ll::service::getLevel()->getPlayerByXuid(xuid); player != nullptr) { 108 | updatePlayerRank(*player); 109 | } 110 | } 111 | 112 | void MainManager::updatePlayerRank(Player& player) { 113 | const types::Rank& rank = getPlayerRankOrSetDefault(player); 114 | 115 | setScoreTag(player, RankFormatsManager::getScoreTagFormat(rank.getName())); 116 | 117 | extraActions(rank, player); 118 | extraVanillaActions(player, rank); 119 | } 120 | 121 | void MainManager::setScoreTag(Player& player, const std::string& scoreTag) { 122 | player.mEntityData->set(static_cast(ActorDataIDs::Score), scoreTag); 123 | } 124 | 125 | void MainManager::extraActions(const types::Rank& rank, const Player& player) { 126 | optional_ref commandRegistry = ll::service::getCommandRegistry(); 127 | if (!commandRegistry) { 128 | return; 129 | } 130 | 131 | AvailableCommandsPacket packet = std::move(commandRegistry->serializeAvailableCommands()); 132 | for (AvailableCommandsPacket::CommandData& command : *packet.mCommands) { 133 | const auto& commandName = *command.name; 134 | if (rank.isCommandAvailable(commandName)) { 135 | command.permission = CommandPermissionLevel::Any; 136 | } 137 | 138 | if (command.permission == CommandPermissionLevel::Any) { 139 | if (command.overloads->empty()) { 140 | continue; 141 | } 142 | 143 | std::vector myOverloads = {}; 144 | for (auto [index, overload] : std::views::enumerate(*command.overloads)) { 145 | if (rank.isCommandOverloadHidden(commandName, static_cast(index))) { 146 | continue; 147 | } 148 | 149 | for (auto& param : *overload.params) { 150 | param.paramOptions = static_cast(CommandParameterOption::None); 151 | } 152 | 153 | myOverloads.push_back(std::move(overload)); 154 | } 155 | 156 | command.overloads = std::move(myOverloads); 157 | } 158 | } 159 | 160 | for (auto& constraint : *packet.mConstraints) { 161 | if (constraint.constraints->empty()) { 162 | continue; 163 | } 164 | 165 | // выбираем только CommandName и нужные нам команды (packet.mEnumValues->at(constraint.enumValueSymbol) 166 | // возвращает имя команды) 167 | if (constraint.enumSymbol != 13 168 | && !rank.isCommandAvailable(packet.mEnumValues->at(constraint.enumValueSymbol))) { 169 | continue; 170 | } 171 | 172 | // убираем constraint 173 | constraint.constraints = {}; 174 | } 175 | 176 | packet.sendToClient(player.getNetworkIdentifier(), player.getClientSubId()); 177 | } 178 | 179 | void MainManager::extraVanillaActions(Player& player, const types::Rank& rank) { 180 | if (rank.isCommandAvailable("teleport")) { 181 | player.setAbility(AbilitiesIndex::Teleport, true); 182 | } 183 | } 184 | 185 | } // namespace power_ranks::manager 186 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Creative Commons Legal Code 2 | 3 | CC0 1.0 Universal 4 | 5 | CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE 6 | LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN 7 | ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS 8 | INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES 9 | REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS 10 | PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM 11 | THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED 12 | HEREUNDER. 13 | 14 | Statement of Purpose 15 | 16 | The laws of most jurisdictions throughout the world automatically confer 17 | exclusive Copyright and Related Rights (defined below) upon the creator 18 | and subsequent owner(s) (each and all, an "owner") of an original work of 19 | authorship and/or a database (each, a "Work"). 20 | 21 | Certain owners wish to permanently relinquish those rights to a Work for 22 | the purpose of contributing to a commons of creative, cultural and 23 | scientific works ("Commons") that the public can reliably and without fear 24 | of later claims of infringement build upon, modify, incorporate in other 25 | works, reuse and redistribute as freely as possible in any form whatsoever 26 | and for any purposes, including without limitation commercial purposes. 27 | These owners may contribute to the Commons to promote the ideal of a free 28 | culture and the further production of creative, cultural and scientific 29 | works, or to gain reputation or greater distribution for their Work in 30 | part through the use and efforts of others. 31 | 32 | For these and/or other purposes and motivations, and without any 33 | expectation of additional consideration or compensation, the person 34 | associating CC0 with a Work (the "Affirmer"), to the extent that he or she 35 | is an owner of Copyright and Related Rights in the Work, voluntarily 36 | elects to apply CC0 to the Work and publicly distribute the Work under its 37 | terms, with knowledge of his or her Copyright and Related Rights in the 38 | Work and the meaning and intended legal effect of CC0 on those rights. 39 | 40 | 1. Copyright and Related Rights. A Work made available under CC0 may be 41 | protected by copyright and related or neighboring rights ("Copyright and 42 | Related Rights"). Copyright and Related Rights include, but are not 43 | limited to, the following: 44 | 45 | i. the right to reproduce, adapt, distribute, perform, display, 46 | communicate, and translate a Work; 47 | ii. moral rights retained by the original author(s) and/or performer(s); 48 | iii. publicity and privacy rights pertaining to a person's image or 49 | likeness depicted in a Work; 50 | iv. rights protecting against unfair competition in regards to a Work, 51 | subject to the limitations in paragraph 4(a), below; 52 | v. rights protecting the extraction, dissemination, use and reuse of data 53 | in a Work; 54 | vi. database rights (such as those arising under Directive 96/9/EC of the 55 | European Parliament and of the Council of 11 March 1996 on the legal 56 | protection of databases, and under any national implementation 57 | thereof, including any amended or successor version of such 58 | directive); and 59 | vii. other similar, equivalent or corresponding rights throughout the 60 | world based on applicable law or treaty, and any national 61 | implementations thereof. 62 | 63 | 2. Waiver. To the greatest extent permitted by, but not in contravention 64 | of, applicable law, Affirmer hereby overtly, fully, permanently, 65 | irrevocably and unconditionally waives, abandons, and surrenders all of 66 | Affirmer's Copyright and Related Rights and associated claims and causes 67 | of action, whether now known or unknown (including existing as well as 68 | future claims and causes of action), in the Work (i) in all territories 69 | worldwide, (ii) for the maximum duration provided by applicable law or 70 | treaty (including future time extensions), (iii) in any current or future 71 | medium and for any number of copies, and (iv) for any purpose whatsoever, 72 | including without limitation commercial, advertising or promotional 73 | purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each 74 | member of the public at large and to the detriment of Affirmer's heirs and 75 | successors, fully intending that such Waiver shall not be subject to 76 | revocation, rescission, cancellation, termination, or any other legal or 77 | equitable action to disrupt the quiet enjoyment of the Work by the public 78 | as contemplated by Affirmer's express Statement of Purpose. 79 | 80 | 3. Public License Fallback. Should any part of the Waiver for any reason 81 | be judged legally invalid or ineffective under applicable law, then the 82 | Waiver shall be preserved to the maximum extent permitted taking into 83 | account Affirmer's express Statement of Purpose. In addition, to the 84 | extent the Waiver is so judged Affirmer hereby grants to each affected 85 | person a royalty-free, non transferable, non sublicensable, non exclusive, 86 | irrevocable and unconditional license to exercise Affirmer's Copyright and 87 | Related Rights in the Work (i) in all territories worldwide, (ii) for the 88 | maximum duration provided by applicable law or treaty (including future 89 | time extensions), (iii) in any current or future medium and for any number 90 | of copies, and (iv) for any purpose whatsoever, including without 91 | limitation commercial, advertising or promotional purposes (the 92 | "License"). The License shall be deemed effective as of the date CC0 was 93 | applied by Affirmer to the Work. Should any part of the License for any 94 | reason be judged legally invalid or ineffective under applicable law, such 95 | partial invalidity or ineffectiveness shall not invalidate the remainder 96 | of the License, and in such case Affirmer hereby affirms that he or she 97 | will not (i) exercise any of his or her remaining Copyright and Related 98 | Rights in the Work or (ii) assert any associated claims and causes of 99 | action with respect to the Work, in either case contrary to Affirmer's 100 | express Statement of Purpose. 101 | 102 | 4. Limitations and Disclaimers. 103 | 104 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 105 | surrendered, licensed or otherwise affected by this document. 106 | b. Affirmer offers the Work as-is and makes no representations or 107 | warranties of any kind concerning the Work, express, implied, 108 | statutory or otherwise, including without limitation warranties of 109 | title, merchantability, fitness for a particular purpose, non 110 | infringement, or the absence of latent or other defects, accuracy, or 111 | the present or absence of errors, whether or not discoverable, all to 112 | the greatest extent permissible under applicable law. 113 | c. Affirmer disclaims responsibility for clearing rights of other persons 114 | that may apply to the Work or any use thereof, including without 115 | limitation any person's Copyright and Related Rights in the Work. 116 | Further, Affirmer disclaims responsibility for obtaining any necessary 117 | consents, permissions or other rights required for any use of the 118 | Work. 119 | d. Affirmer understands and acknowledges that Creative Commons is not a 120 | party to this document and has no duty or obligation with respect to 121 | this CC0 or use of the Work. 122 | -------------------------------------------------------------------------------- /src/mod/commands/EditRankCommand.cpp: -------------------------------------------------------------------------------- 1 | #include "EditRankCommand.h" 2 | #include "../forms/EditRankForm.h" 3 | #include "../manager/rankFormats/RankFormatsManager.h" 4 | #include "../manager/ranks/RanksManager.h" 5 | #include "../utils/Utils.h" 6 | #include 7 | 8 | namespace power_ranks::commands { 9 | 10 | void EditRankCommand::executeFirstParameter( 11 | const CommandOrigin& origin, 12 | CommandOutput& output, 13 | const FirstParameter& parameter, 14 | [[maybe_unused]] const Command& _ 15 | ) { 16 | if (origin.getEntity() == nullptr || !origin.getEntity()->isType(ActorType::Player)) { 17 | output.error(manager::LanguageManager::getTranslate("commandEditRankUsing")); 18 | return; 19 | } 20 | 21 | ServerPlayer& player = static_cast(*origin.getEntity()); 22 | 23 | if (manager::ConfigManager::getConfig().superRanks.contains(parameter.rankName)) { 24 | output.error(manager::LanguageManager::getTranslate("editRankSuperRank", player.getLocaleCode())); 25 | return; 26 | } 27 | 28 | if (!manager::RankFormatsManager::isKnownLocaleCode(parameter.localeCode, true)) { 29 | output.error( 30 | Utils::strReplace( 31 | manager::LanguageManager::getTranslate("editRankSecondInvalidLocaleCode", player.getLocaleCode()), 32 | "{defaultLocaleCode}", 33 | manager::ConfigManager::getConfig().defaultLocaleCode 34 | ) 35 | ); 36 | return; 37 | } 38 | 39 | const auto& rank = manager::RanksManager::getRank(parameter.rankName); 40 | if (!rank.has_value() || rank.value() == nullptr) { 41 | std::string ranks = ""; 42 | for (const auto& [name, otherRank] : manager::RanksManager::getRanks()) { 43 | if (ranks.empty()) { 44 | ranks = name; 45 | continue; 46 | } 47 | 48 | ranks += ", " + name; 49 | } 50 | 51 | output.error( 52 | Utils::strReplace( 53 | manager::LanguageManager::getTranslate("undefinedRank", player.getLocaleCode()), 54 | {"{rankName}", "{ranks}"}, 55 | {parameter.rankName, std::move(ranks)} 56 | ) 57 | ); 58 | return; 59 | } 60 | 61 | forms::EditRankForm::init(static_cast(*origin.getEntity()), rank.value(), parameter.localeCode); 62 | } 63 | 64 | void EditRankCommand::executeSecondParameter( 65 | const CommandOrigin& origin, 66 | CommandOutput& output, 67 | const SecondParameter& parameter, 68 | [[maybe_unused]] const Command& _ 69 | ) { 70 | // clang-format off 71 | bool isOriginServer = origin.getEntity() == nullptr || !origin.getEntity()->isType(ActorType::Player); 72 | const auto& localeCode = isOriginServer ? manager::ConfigManager::getConfig().defaultLocaleCode : static_cast(*origin.getEntity()).getLocaleCode(); 73 | 74 | if (!isOriginServer && manager::ConfigManager::getConfig().superRanks.contains(parameter.rankName)) { 75 | // clang-format on 76 | output.error(manager::LanguageManager::getTranslate("editRankSuperRank", localeCode)); 77 | return; 78 | } 79 | 80 | if (!manager::RankFormatsManager::isKnownLocaleCode(parameter.localeCode, true)) { 81 | output.error(manager::LanguageManager::getTranslate("editRankFirstInvalidLocaleCode", localeCode)); 82 | return; 83 | } 84 | 85 | const auto& rank = manager::RanksManager::getRank(parameter.rankName); 86 | if (!rank.has_value() || rank.value() == nullptr) { 87 | std::string ranks = ""; 88 | for (const auto& [name, otherRank] : manager::RanksManager::getRanks()) { 89 | if (ranks.empty()) { 90 | ranks = name; 91 | continue; 92 | } 93 | 94 | ranks += ", " + name; 95 | } 96 | 97 | output.error( 98 | Utils::strReplace( 99 | manager::LanguageManager::getTranslate("undefinedRank", localeCode), 100 | {"{rankName}", "{ranks}"}, 101 | {parameter.rankName, std::move(ranks)} 102 | ) 103 | ); 104 | return; 105 | } 106 | 107 | const auto& inheritanceRank = manager::RanksManager::getRank(parameter.inheritanceRank); 108 | if (parameter.inheritanceRank != "null" && !inheritanceRank.has_value()) { 109 | std::string ranks = ""; 110 | for (const auto& [name, otherRank] : manager::RanksManager::getRanks()) { 111 | if (ranks.empty()) { 112 | ranks = name; 113 | continue; 114 | } 115 | 116 | ranks += ", " + name; 117 | } 118 | 119 | output.error( 120 | Utils::strReplace( 121 | manager::LanguageManager::getTranslate("undefinedRank", localeCode), 122 | {"{rankName}", "{ranks}"}, 123 | {parameter.inheritanceRank, std::move(ranks)} 124 | ) 125 | ); 126 | return; 127 | } 128 | 129 | const auto& availableCommands = Utils::strSplit(parameter.availableCommands, ";"); 130 | if (parameter.availableCommands != "null" && (availableCommands.empty() || availableCommands.front().empty())) { 131 | output.error(manager::LanguageManager::getTranslate("editRankInvalidFormatAvailableCommands", localeCode)); 132 | return; 133 | } 134 | 135 | const auto& additionalInformation = Utils::strSplit(parameter.additionalInformation, ";"); 136 | if (parameter.additionalInformation != "null" 137 | && (additionalInformation.empty() || additionalInformation.front().empty())) { 138 | output.error(manager::LanguageManager::getTranslate("editRankInvalidFormatAdditionalInformation", localeCode)); 139 | return; 140 | } 141 | 142 | if (inheritanceRank.has_value()) { 143 | rank.value()->setInheritanceRank(inheritanceRank.value()); 144 | } else { 145 | rank.value()->removeInheritanceRank(); 146 | } 147 | 148 | if (parameter.availableCommands != "null") { 149 | rank.value()->setAvailableCommands({availableCommands.begin(), availableCommands.end()}); 150 | } else { 151 | rank.value()->setAvailableCommands({}); 152 | } 153 | 154 | rank.value()->getHiddenCommandOverloads().updateFromString(parameter.hiddenCommandOverloads); 155 | 156 | if (parameter.additionalInformation != "null") { 157 | rank.value()->setAdditionalInformation(additionalInformation); 158 | } else { 159 | rank.value()->setAvailableCommands({}); 160 | } 161 | 162 | manager::RankFormatsManager::setRankFormat( 163 | rank.value()->getName(), 164 | parameter.prefix, 165 | parameter.chat, 166 | parameter.scoreTag, 167 | parameter.localeCode 168 | ); 169 | manager::RanksManager::saveChangesRank(*rank.value()); 170 | 171 | output.success( 172 | Utils::strReplace( 173 | manager::LanguageManager::getTranslate("editRankSuccess", localeCode), 174 | "{rankName}", 175 | parameter.rankName 176 | ) 177 | ); 178 | } 179 | 180 | void EditRankCommand::executeWithoutParameter(const CommandOrigin& origin, CommandOutput& output) { 181 | const auto& localeCode = origin.getEntity() == nullptr || !origin.getEntity()->isType(ActorType::Player) 182 | ? manager::ConfigManager::getConfig().defaultLocaleCode 183 | : static_cast(*origin.getEntity()).getLocaleCode(); 184 | 185 | output.error(manager::LanguageManager::getTranslate("commandEditRankUsing", localeCode)); 186 | } 187 | 188 | } // namespace power_ranks::commands -------------------------------------------------------------------------------- /src/mod/forms/EditRankForm.cpp: -------------------------------------------------------------------------------- 1 | #include "EditRankForm.h" 2 | #include "../manager/lang/LanguageManager.h" 3 | #include "../manager/rankFormats/RankFormatsManager.h" 4 | #include "../manager/ranks/RanksManager.h" 5 | #include "../utils/Utils.h" 6 | #include 7 | 8 | namespace power_ranks::forms { 9 | 10 | void EditRankForm::init(Player& player, types::Rank* rank, const std::string& localeCode) { 11 | ll::form::CustomForm form( 12 | Utils::strReplace( 13 | manager::LanguageManager::getTranslate("formEditRankTitle", player.getLocaleCode()), 14 | "{rankName}", 15 | rank->getName() 16 | ) 17 | ); 18 | 19 | form.appendLabel( 20 | Utils::strReplace( 21 | manager::LanguageManager::getTranslate("formEditRankLabel", player.getLocaleCode()), 22 | "{localeCode}", 23 | localeCode 24 | ) 25 | ); 26 | 27 | form.appendInput( 28 | "prefix", 29 | manager::LanguageManager::getTranslate("formEditRankInputPrefix", player.getLocaleCode()), 30 | manager::LanguageManager::getTranslate("formEditRankInputPrefixPlaceholder", player.getLocaleCode()), 31 | manager::RankFormatsManager::getRawPrefixFormat(rank->getName(), localeCode) 32 | ); 33 | form.appendInput( 34 | "chatFormat", 35 | manager::LanguageManager::getTranslate("formEditRankInputChatFormat", player.getLocaleCode()), 36 | manager::LanguageManager::getTranslate("formEditRankInputChatFormatPlaceholder", player.getLocaleCode()), 37 | manager::RankFormatsManager::getRawChatFormat(rank->getName(), localeCode) 38 | ); 39 | form.appendInput( 40 | "scoreTagFormat", 41 | manager::LanguageManager::getTranslate("formEditRankInputScoreTagFormat", player.getLocaleCode()), 42 | manager::LanguageManager::getTranslate("formEditRankInputScoreTagFormatPlaceholder", player.getLocaleCode()), 43 | manager::RankFormatsManager::getRawScoreTagFormat(rank->getName(), localeCode) 44 | ); 45 | 46 | nlohmann::ordered_map> ranks = { 47 | {manager::LanguageManager::getTranslate("dropdownDontPoint", player.getLocaleCode()), std::nullopt} 48 | }; 49 | for (const auto& [name, rank] : manager::RanksManager::getOrderedRanks()) { 50 | ranks[std::format("{} - {}", name, manager::RankFormatsManager::getPrefixFormat(rank->getName()))] = rank; 51 | } 52 | 53 | const auto& inheritanceRank = rank->getInheritanceRank(); 54 | 55 | size_t index = 0; 56 | std::vector rankNames = {}; 57 | 58 | size_t currentIndex = 0; 59 | for (const auto& [rankName, rank] : ranks) { 60 | if (inheritanceRank.has_value() && rank.has_value() && *inheritanceRank == *rank) { 61 | index = currentIndex; 62 | } 63 | 64 | rankNames.push_back(rankName); 65 | currentIndex++; 66 | } 67 | 68 | form.appendDropdown( 69 | "inheritanceRankName", 70 | manager::LanguageManager::getTranslate("formEditRankDropdownInheritanceRanks", player.getLocaleCode()), 71 | rankNames, 72 | index 73 | ); 74 | form.appendInput( 75 | "availableCommands", 76 | manager::LanguageManager::getTranslate("formEditRankInputAvailableCommands", player.getLocaleCode()), 77 | manager::LanguageManager::getTranslate("formEditRankInputAvailableCommandsPlaceholder", player.getLocaleCode()), 78 | Utils::separateContainer(rank->getAvailableCommands(), ";") 79 | ); 80 | form.appendInput( 81 | "hiddenCommandOverloads", 82 | manager::LanguageManager::getTranslate("formEditRankInputHiddenCommandOverloads", player.getLocaleCode()), 83 | manager::LanguageManager::getTranslate( 84 | "formEditRankInputHiddenCommandOverloadsPlaceholder", 85 | player.getLocaleCode() 86 | ), 87 | rank->getHiddenCommandOverloads().toString() 88 | ); 89 | form.appendInput( 90 | "additionalInformation", 91 | manager::LanguageManager::getTranslate("formEditRankInputAdditionalInformation", player.getLocaleCode()), 92 | manager::LanguageManager::getTranslate( 93 | "formEditRankInputAdditionalInformationPlaceholder", 94 | player.getLocaleCode() 95 | ), 96 | Utils::separateContainer(rank->getAdditionalInformation(), ";") 97 | ); 98 | 99 | form.sendTo( 100 | player, 101 | [rank, localeCode = localeCode, ranks = std::move(ranks)]( 102 | Player& player, 103 | const ll::form::CustomFormResult& result, 104 | ll::form::FormCancelReason reason 105 | ) -> void { 106 | if (reason.has_value()) { 107 | return; 108 | } 109 | 110 | std::string prefix; 111 | std::string chatFormat; 112 | std::string scoreTagFormat; 113 | std::optional inheritanceRank; 114 | std::string availableCommands; 115 | std::string hiddenCommandOverloads; 116 | std::string additionalInformation; 117 | 118 | try { 119 | prefix = std::get_if(&result->at("prefix"))->data(); 120 | chatFormat = std::get_if(&result->at("chatFormat"))->data(); 121 | scoreTagFormat = std::get_if(&result->at("scoreTagFormat"))->data(); 122 | inheritanceRank = ranks[std::get_if(&result->at("inheritanceRankName"))->data()]; 123 | availableCommands = std::get_if(&result->at("availableCommands"))->data(); 124 | hiddenCommandOverloads = std::get_if(&result->at("hiddenCommandOverloads"))->data(); 125 | additionalInformation = std::get_if(&result->at("additionalInformation"))->data(); 126 | } catch (...) { 127 | player.sendMessage(manager::LanguageManager::getTranslate("undefinedError", player.getLocaleCode())); 128 | return; 129 | } 130 | 131 | if (prefix.empty() || chatFormat.empty()) { 132 | player.sendMessage(manager::LanguageManager::getTranslate("formIncorrectData", player.getLocaleCode())); 133 | return; 134 | } 135 | 136 | std::vector availableCommandsVector = Utils::strSplit(availableCommands, ";"); 137 | if (availableCommands != "null" 138 | && (availableCommandsVector.empty() || availableCommandsVector.front().empty())) { 139 | player.sendMessage( 140 | manager::LanguageManager::getTranslate( 141 | "editRankInvalidFormatAvailableCommands", 142 | player.getLocaleCode() 143 | ) 144 | ); 145 | return; 146 | } 147 | 148 | std::vector additionalInformationVector = Utils::strSplit(additionalInformation, ";"); 149 | if (additionalInformation != "null" 150 | && (additionalInformationVector.empty() || additionalInformationVector.front().empty())) { 151 | player.sendMessage( 152 | manager::LanguageManager::getTranslate( 153 | "editRankInvalidFormatAdditionalInformation", 154 | player.getLocaleCode() 155 | ) 156 | ); 157 | return; 158 | } 159 | 160 | if (inheritanceRank.has_value()) { 161 | rank->setInheritanceRank(*inheritanceRank); 162 | } else { 163 | rank->removeInheritanceRank(); 164 | } 165 | 166 | if (availableCommands != "null") { 167 | rank->setAvailableCommands({availableCommandsVector.begin(), availableCommandsVector.end()}); 168 | } else { 169 | rank->setAvailableCommands({}); 170 | } 171 | 172 | rank->getHiddenCommandOverloads().updateFromString(hiddenCommandOverloads); 173 | 174 | if (additionalInformation != "null") { 175 | rank->setAdditionalInformation(additionalInformationVector); 176 | } else { 177 | rank->setAvailableCommands({}); 178 | } 179 | 180 | manager::RankFormatsManager::setRankFormat(rank->getName(), prefix, chatFormat, scoreTagFormat, localeCode); 181 | manager::RanksManager::saveChangesRank(*rank); 182 | 183 | player.sendMessage( 184 | Utils::strReplace( 185 | manager::LanguageManager::getTranslate("editRankSuccess", player.getLocaleCode()), 186 | "{rankName}", 187 | rank->getName() 188 | ) 189 | ); 190 | } 191 | ); 192 | } 193 | 194 | } // namespace power_ranks::forms --------------------------------------------------------------------------------