diff --git a/CHANGELOG.md b/CHANGELOG.md index 70dacf3..f0b2f9d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,27 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.0.0] - 2024-8-23 + +### Added + +- 新增 “模板生成器” 和 “模板记录器” +- Config.generator 新增 `type`、`templateFile` 配置 +- 新增 Radius、Cube 类 + +### Changed + +- 重构 EconomicSystem、PlotPos +- Config.moneys 改为 Config.economy +- GUI 支持地皮管理员操作 +- 地皮世界填充平原群系 +- 重构权限系统(细分权限) + +### Removed + +- 移除 RemoteCall API +- 移除 Config.plotWorld.eventListener.onUseItemOnWhiteList + ## [0.6.1] - 2024-07-24 ### Changed diff --git a/README.md b/README.md index 96a9332..d822c89 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,24 @@ # PlotCraft -基于 LeviLamina 开发,适用于 BDS(Bedrock Server)的地皮插件! - -- 地皮维度/主世界维度 自由选择 -- 可自定义地皮大小 -- 可自定义方块类型 -- 玩家地皮出售系统 -- 玩家地皮评论系统 -- 玩家自定义设置 -- ... - -## 安装/使用/开发 - -### 命令安装 - -推荐使用 Lip 一键安装(自动处理依赖) +基于 LeviLamina & MoreDimension 开发,适用于 BDS(Bedrock Server)的地皮插件(系统)! + +- [x] 独立维度 / 主世界维度 自由选择 +- [x] 可自定义地皮大小 +- [x] 可自定义地皮方块 +- [x] 玩家地皮出售系统 +- [x] 玩家地皮评论系统 +- [x] 玩家自定义设置 +- [x] 地皮传送 +- [x] 地皮平原群系 +- [x] 地皮模板系统 + - [x] 模板生成器 + - 按模板生成地皮 + - [x] 模板记录器 + - 制作地皮模板 + +## 安装 + +### Lip ```bash lip install github.com/engsr6982/PlotCraft @@ -27,14 +31,12 @@ lip install github.com/engsr6982/PlotCraft - MoreDimension - LegacyMoney -- LegacyRemoteCall > Tip -> 编译目标为 Overwload 时 MoreDimension 无需安装 -> 未编译 RemoteCall 相关代码时,LegacyRemoteCall 为可选 +> 编译目标 Overwload 为 true 时 MoreDimension 无需安装 > 插件默认版本是依赖上述组件,特殊版本需自行从源编译 -### 使用 +## 命令 插件安装完毕后,启动服务器(BDS),进入服务器 传送至对应维度,插件将会生成地皮地形 @@ -52,72 +54,84 @@ PlotCraft 注册了以下命令: /plo 打开地皮系统主菜单 /plo db save 立即保存数据到数据库 /plo buy 购买脚下地皮(出售状态) -``` - -> [warning] -> 插件压缩包内 lse 文件夹中,附带了一个 Js 文件 `PlotCraft-Fixer.js` -> 此文件用于修复插件本体未处理的事件,如果需要使用请将其放入`plugins/`目录下, 由 LSE(LegacyScriptEngine) 引擎加载。 -### 开发 & 扩展 & 贡献 - -PlotCraft 提供 SDK 和 RemoteCall API。 +地皮模板记录命令(此功能请查看下文 "地皮模板系统") +/plo template record execute +/plo template record pos1 +/plo template record pos2 +/plo template record reset +/plo template record start +``` -#### SDK 开发 +## 地皮模板系统 -使用 SDK 的优势在于,您能访问 PlotCraft 几乎全部的 API,使用 C++开发 +PlotCraft 提供了地皮模板功能,您可以通过模板生成地皮,也可以将地皮制作成模板。 -SDK 可在 Release 页面下载 +模板系统包括: -#### RemoteCall 开发 +- 模板生成器 + - 模板生成器可以将模板文件生成地皮,您需要先制作好模板文件,然后使用模板生成器生成地皮 +- 模板记录器 + - 模板记录器可以将地皮制作成模板,您需要先选择好地皮,然后使用模板记录器制作模板 -RemoteCall 为 LSE 引擎提供的远程调用 API,提供基础的交互能力。 -但受限于 RemoteCall,仅能访问 PlotCraft 部分已导出的 API +详细使用方法请参考 [模板使用文档](./docs/TemplateSystem.md) -API 声明、封装文件,可在每个 Release 版本压缩包里找到 -开发可参考 PlotCraft-Fixer.js +## 开发 & 扩展 -#### 贡献 +PlotCraft 提供 SDK 包,您可以通过 SDK 包扩展 PlotCraft 的功能 -我们欢迎您 Pr 本插件,为本插件增加更多功能!(^ ω ^)ノ 🎉 +SDK 包可在 Release 页面下载 ## 配置文件 ```json { - "version": 3, // 配置文件版本(请勿修改) + "version": 7, "generator": { - // 地皮生成器配置 - // 生成器不支持动态修改,请一次确定好生成器配置再进入地皮世界 - // 如果动态修改,则100%导致已生成的区块和未生成区块之间前街错误(地形错误) + // 注意: + // 请确定好生成器配置再进入地皮世界 + // 地皮世界生成地形后,请不要再修改生成器配置,否则会出现已生成的区块和未生成区块之间前街错误(地形错误) + // 如果修改生成器导致的区块衔接错误,请使用地图编辑器删除错位的区块(Amulet) + + "type": "Default", // 生成器类型 Default 或 Template + + // Default 生成器配置: "plotWidth": 64, // 地皮大小(正方形) "roadWidth": 5, // 道路宽度 "subChunkNum": 4, // 子区块数量(生成的子区块数量,4 * 16 = 64 即世界高度为 0 ) "roadBlock": "minecraft:cherry_planks", // 道路方块 "fillBlock": "minecraft:grass_block", // 填充方块 - "borderBlock": "minecraft:stone_block_slab" // 边界方块 - }, - "moneys": { - "Enable": false, - "MoneyType": "LLMoney", // LLMonet / ScoreBoard - "MoneyName": "money", - "ScoreName": "" + "borderBlock": "minecraft:stone_block_slab", // 边界方块 + + // Template 生成器配置: + "templateFile": "TestTemplate.json" // 模板文件名,模板文件必须放置在 config 目录下 }, - "switchDim": { - "overWorld": [-89.56292724609375, 72.62001037597656, -164.71534729003906], // 切换维度时传送的坐标 xyz(可在游戏中设置) - "plotWorld": [0.7177982926368713, 2.1200098991394043, 0.3800940215587616] + "economy": { + "enable": false, // 是否启用经济系统 + "type": "LegacyMoney", // 经济系统类型 LegacyMoney / ScoreBoard + "scoreName": "", // 计分板名称 + "economicName": "金币" // 经济名称 }, "plotWorld": { - "maxBuyPlotCount": 10, // 最大可购买地皮数量 - "buyPlotPrice": 10000, // 购买地皮价格 - "inPlotCanFly": true, // 地皮内可飞行 - "spawnMob": false, // 是否生成生物 + "maxBuyPlotCount": 25, // 玩家最大持有地皮数量 + "buyPlotPrice": 1000, // 购买地皮价格 + "inPlotCanFly": true, // 是否启用地皮飞行 "playerSellPlotTax": 0.1, // 玩家出售地皮税率 + "spawnMob": false, // 地皮世界是否生成实体 + "eventListener": { - "onSculkSpreadListener": true, // 是否启用SculkSpread事件监听器 - "onSculkBlockGrowthListener": true, // 是否启用 SculkBlockGrowth事件监听器 - "onUseItemOnWhiteList": ["minecraft:clock"] // 玩家右键使用物品白名单 + "onSculkSpreadListener": true, // 禁止幽匿块蔓延(地皮维度) + "onSculkBlockGrowthListener": true // 禁止幽匿尖啸体生成(地皮维度) } }, - "allowedPlotTeleportDim": [0, 1, 2, 3] // 允许传送的地皮的维度 + "switchDim": { + // 地皮维度和主世界切换传送坐标,此项可在游戏中设置 + "overWorld": [0.0, 100.0, 0.0], + "plotWorld": [0.5, 0.0, 0.5] + }, + "allowedPlotTeleportDim": [ + // 允许从以下维度传送到地皮 + 0, 1, 2, 3 + ] } ``` diff --git a/assets/lang/zh_CN.json b/assets/lang/zh_CN.json index 5a62873..5ef0d09 100644 --- a/assets/lang/zh_CN.json +++ b/assets/lang/zh_CN.json @@ -1,20 +1,73 @@ { "showPlotTip": "是否显示底部地皮提示", - "canDestroyBlock": "是否允许破坏方块", - "canPlaceBlock": "是否允许放置方块", - "canUseItemOn": "是否允许使用物品(右键)", - "canFireSpread": "是否允许火焰蔓延", - "canAttack": "是否允许攻击", - "canPickupItem": "是否允许拾取物品", - "canInteractBlock": "是否允许与方块交互", - "canFarmLandDecay": "耕地退化", - "canOperateFrame": "操作展示框", - "canMobHurt": "生物受伤", - "canAttackBlock": "攻击方块", - "canOperateArmorStand": "操作盔甲架", - "canDropItem": "丢弃物品", - "canStepOnPressurePlate": "踩压压力板", - "canRide": "骑乘", - "canWitherDestroyBlock": "凋零破坏方块", - "canRedStoneUpdate": "红石更新" + "allowFireSpread": "火焰蔓延", + "allowAttackDragonEgg": "攻击龙蛋(右键)", + "allowFarmDecay": "耕地退化", + "allowPistonPush": "活塞推动", + "allowRedstoneUpdate": "红石更新", + "allowExplode": "爆炸", + "allowDestroy": "允许破坏", + "allowWitherDestroy": "允许凋零破坏", + "allowPlace": "允许放置(方块、头颅、旗帜、等)", + "allowAttackPlayer": "允许攻击玩家", + "allowAttackAnimal": "允许攻击动物", + "allowAttackMob": "允许攻击实体(非动物)", + "allowOpenChest": "允许打开箱子", + "allowPickupItem": "允许拾取物品", + "allowThrowSnowball": "允许投掷雪球", + "allowThrowEnderPearl": "允许投掷末影珍珠", + "allowThrowEgg": "允许投掷鸡蛋", + "allowThrowTrident": "允许投掷三叉戟", + "allowDropItem": "允许丢弃物品", + "allowShoot": "允许射击(弩、箭)", + "allowThrowPotion": "允许投掷药水(投掷型、滞留型)", + "allowRideEntity": "允许骑乘实体(玩家)", + "allowRideTrans": "允许骑乘矿车、船(玩家)", + "allowAxePeeled": "允许斧头去皮", + "allowAttackEnderCrystal": "允许攻击末地水晶", + "allowDestroyArmorStand": "允许破坏盔甲架", + "useAnvil": "使用铁砧", + "useBarrel": "使用木桶", + "useBeacon": "使用信标", + "useBed": "使用床", + "useBell": "使用钟", + "useBlastFurnace": "使用高炉", + "useBrewingStand": "使用酿造台", + "useCampfire": "使用营火", + "useFiregen": "使用打火石", + "useCartographyTable": "使用制图台", + "useComposter": "使用堆肥桶", + "useCraftingTable": "使用工作台", + "useDaylightDetector": "使用阳光探测器", + "useDispenser": "使用发射器", + "useDropper": "使用投掷器", + "useEnchantingTable": "使用附魔台", + "useDoor": "使用门", + "useFenceGate": "使用栅栏门", + "useFurnace": "使用熔炉", + "useGrindstone": "使用砂轮", + "useHopper": "使用漏斗", + "useJukebox": "使用唱片机", + "useLoom": "使用织布机", + "useStonecutter": "使用切石机", + "useNoteBlock": "使用音符盒", + "useShulkerBox": "使用潜影盒", + "useSmithingTable": "使用锻造台", + "useSmoker": "使用烟熏炉", + "useTrapdoor": "使用活板门", + "useLectern": "使用讲台", + "useCauldron": "使用炼药锅", + "useLever": "使用拉杆", + "useButton": "使用按钮", + "useRespawnAnchor": "使用重生锚", + "useItemFrame": "使用物品展示框", + "useFishingHook": "使用钓鱼竿", + "useBucket": "使用桶(各种桶)", + "usePressurePlate": "使用压力板(踩踏)", + "useArmorStand": "使用盔甲架(编辑)", + "useBoneMeal": "使用骨粉", + "useHoe": "使用锄头(耕地)", + "useShovel": "使用锹(铲除草径)", + "editFlowerPot": "编辑花盆(种花、树、等)", + "editSign": "编辑告示牌" } \ No newline at end of file diff --git a/docs/TemplateSystem.md b/docs/TemplateSystem.md new file mode 100644 index 0000000..f38e960 --- /dev/null +++ b/docs/TemplateSystem.md @@ -0,0 +1,121 @@ +# 地皮模板系统 + +地皮模板系统分为: + +- [模板记录器](#模板记录器) +- [模板生成器](#模板生成器) + +## 模板记录器 + +“模板记录器” 又名 “模板制作器”,用于记录模板信息,并生成模板文件。 + +### 准备工作 + +1. 安装 Levilamina 并带有 PlotCraft 插件(模组) +2. 准备一张超平坦地图(由于模板生成的特性,建议使用超平坦地图) +3. 启动服务端,并使用 `/plo op ` 将自己添加为管理员 + +### 模板建造 + +1. 在前面准备工作完成后,进入服务器 + +2. 进入服务器后,我们首先找到 (0,0) 区块 + +![chunk](./image/chunk_0_0.png) + +> Tip +> Minecraft 的每个区块大小为 16x16 +> 我们可以直接使用 `/tp 0 ~ 0` 命令快速到达 (0,0) 区块 + +3. 在找到 (0,0) 区块后,我们要确定模板的大小(区块数量) + +> Tip +> 一个区块的长款为 16x16 +> 地皮大小 = 区块 \* 16 +> 例如:我们需要一个 48x48 大小的地皮,那么我们需要 3x3 个区块 + +4. 确定好模板大小后,我们需要找到对角的区块 + +> Tip +> 例如:我们需要一个 48x48 大小的地皮,那么我们需要 3x3 个区块,那么对角区块就是 (2,2) 区块 +> 我们可以直接使用命令传送到计算出来的对角区块 `/tp 47 ~ 47` + +![chunk_3](./image/chunk_3.png) + +> 为了演示方便,笔者使用方块圈出了范围 + +5. 在确定以上信息后,我们可以开始建造模板了 + +![test](./image/test_chunk.png) + +> 为了演示,随便种了点樱花树 + +### 模板记录 + +1. 在模板建造好后,我们回到 (0,0) 区块,执行以下命令 + +``` +/plo template record start +``` + +> Tip: +> 为模板开始层(开始层以下的方块都不会被记录) +> 为模板结束层(结束层以上的方块都不会被记录) +> 为模板道路的宽度(由于模板是 1:1 生成,道路宽度会被\*2,这里填写原始值即可) +> 为是否填充底部基岩 +> 为默认填充方块(因为 starty 以下的方块不会被记录,模板会使用此默认方块进行生成) + +![cmd_start](./image/cmd_start.png) + +2. 在执行完命令后,再执行 `/plo template record pos1` 设置点 1 + +3. 设置点 1 后,我们去到对角区块,执行命令 `/plo template record pos2` 设置点 2 + +4. 最后执行以下命令,插件将开始记录模板 + +``` +/plo template record execute +``` + +> Tip: +> 为模板文件名 + +![executed](./image/executed.png) + +> Tip: +> 记录完成后或者出现错误,可以使用`/plo template record reset`命令重置模板 +> 重置后需重新执行 1~4 步 + +## 模板生成器 + +在前面我们完成了模板记录后,我们就可以使用模板生成器来生成模板了 + +1. 关闭服务器,打开插件目录下的 `config.json` 文件 + +2. 修改 `generator.type` 为 `Template` + +3. 在 `generator.templateFile` 处填写刚刚生成的模板文件名 + +```json +{ + "generator": { + "type": "Template", + "templateFile": "Test.json" + } +} +``` + +> Tip +> 笔者演示时生成的文件为 `Test.json`,请根据实际情况填写 + +> ⚠ Warning +> 模板文件需要放在 config 目录下,路径中不能含有任何中文字符 +> 配置好模板生成器后,需删除存档或使用 Amulet 删除地皮维度区块使生成器重新生成区块 + +4. 在配置好 config 后,启动服务器,插件(模组)会在 Enable 阶段解析并加载模板 + +![](./image/test_template.png) + +5. 进入服务器,传送到地皮维度,即可看到生成的模板 + +![gen](./image/template_generator_test.png) diff --git a/docs/image/chunk_0_0.png b/docs/image/chunk_0_0.png new file mode 100644 index 0000000..9a354bb Binary files /dev/null and b/docs/image/chunk_0_0.png differ diff --git a/docs/image/chunk_3.png b/docs/image/chunk_3.png new file mode 100644 index 0000000..9b4e7d2 Binary files /dev/null and b/docs/image/chunk_3.png differ diff --git a/docs/image/cmd_start.png b/docs/image/cmd_start.png new file mode 100644 index 0000000..ce32201 Binary files /dev/null and b/docs/image/cmd_start.png differ diff --git a/docs/image/executed.png b/docs/image/executed.png new file mode 100644 index 0000000..a62a344 Binary files /dev/null and b/docs/image/executed.png differ diff --git a/docs/image/template_generator_test.png b/docs/image/template_generator_test.png new file mode 100644 index 0000000..68338ab Binary files /dev/null and b/docs/image/template_generator_test.png differ diff --git a/docs/image/test_chunk.png b/docs/image/test_chunk.png new file mode 100644 index 0000000..ae05acd Binary files /dev/null and b/docs/image/test_chunk.png differ diff --git a/docs/image/test_template.png b/docs/image/test_template.png new file mode 100644 index 0000000..5c18484 Binary files /dev/null and b/docs/image/test_template.png differ diff --git a/include/plotcraft/Config.h b/include/plotcraft/Config.h index e539548..b2aae71 100644 --- a/include/plotcraft/Config.h +++ b/include/plotcraft/Config.h @@ -1,7 +1,8 @@ #pragma once +#include "Version.h" #include "ll/api/Config.h" #include "plotcraft/Macro.h" -#include "plotcraft/utils/Moneys.h" +#include "plotcraft/utils/EconomySystem.h" #include #include @@ -10,40 +11,26 @@ using string = std::string; namespace plo::config { -#define mConfigVersion 6 - +enum class PlotGeneratorType : int { Default, Template }; struct _Config { - int version = mConfigVersion; + int version = CONFIG_VERSION; -#ifdef GEN_1 struct _Generator { - int plotWidth = 64; // 地皮大小 - int roadWidth = 5; // 道路宽度 - int subChunkNum = 4; // 子区块数量(负责高度,n*16) + PlotGeneratorType type = PlotGeneratorType::Default; // 生成器类型 + // Default Generator + int plotWidth = 64; // 地皮大小 + int roadWidth = 5; // 道路宽度 + int subChunkNum = 4; // 子区块数量(负责高度,n*16) string roadBlock = "minecraft:cherry_planks"; string fillBlock = "minecraft:grass_block"; string borderBlock = "minecraft:stone_block_slab"; - } generator; -#endif - -#ifdef GEN_2 - struct _Generator { - int plotChunkSize = 3; // 地皮区块 - int subChunkNum = 0; // TODO: 子区块数量(负责高度,n*16) - string roadBlock = "minecraft:cherry_planks"; - string borderBlock = "minecraft:stone_block_slab"; + // Tempplate Generator + string templateFile = ""; } generator; -#endif - utils::MoneysConfig moneys; // 经济系统配置 - - struct SwitchDim { - std::vector overWorld = {0, 100, 0}; // 切换维度时传送的坐标 - - std::vector plotWorld = {0.5, 0, 0.5}; - } switchDim; + utils::EconomyConfig economy; // 经济系统配置 struct PlotWorld { int maxBuyPlotCount = 25; // 最大购买地皮数量 @@ -53,14 +40,22 @@ struct _Config { bool spawnMob = false; // 地皮世界是否生成生物 + // int maxMergePlotCount = 4; // 最大合并地皮数量 + // int baseMergePlotPrice = 1000; // 基础合并地皮价格 + // double mergePriceMultiplier = 1.1; // 合并价格倍率,默认为1.0(保持基础价格不变) + struct EventListener { bool onSculkSpreadListener{true}; bool onSculkBlockGrowthListener{true}; - - std::vector onUseItemOnWhiteList = {"minecraft:clock"}; } eventListener; } plotWorld; + struct SwitchDim { + std::vector overWorld = {0, 100, 0}; // 切换维度时传送的坐标 + + std::vector plotWorld = {0.5, 0, 0.5}; + } switchDim; + std::vector allowedPlotTeleportDim = {0, 1, 2, 3}; // 允许传送到地皮维度的维度列表 }; @@ -70,4 +65,6 @@ PLAPI void loadConfig(); PLAPI void updateConfig(); +// PLAPI double calculateMergePlotPrice(int mergeCount); + } // namespace plo::config \ No newline at end of file diff --git a/include/plotcraft/EconomyQueue.h b/include/plotcraft/EconomyQueue.h index 11a33f5..f75948b 100644 --- a/include/plotcraft/EconomyQueue.h +++ b/include/plotcraft/EconomyQueue.h @@ -2,7 +2,7 @@ #include "data/PlotMetadata.h" #include "mc/deps/core/mce/UUID.h" #include "plotcraft/Macro.h" -#include "plotcraft/utils/Moneys.h" +#include "plotcraft/utils/EconomySystem.h" #include "plugin/MyPlugin.h" #include #include @@ -15,16 +15,16 @@ #include -using UUID = plo::data::UUID; using json = nlohmann::json; namespace fs = std::filesystem; namespace plo { +using namespace data; class EconomyQueue { public: - std::shared_ptr>>> mQueue; // 队列 + std::shared_ptr>>> mQueue; // 队列 fs::path mPath; // 文件路径 @@ -34,13 +34,13 @@ class EconomyQueue { PLAPI static EconomyQueue& getInstance(); - PLAPI bool has(UUID const& target) const; + PLAPI bool has(UUIDs const& target) const; - PLAPI std::shared_ptr> get(UUID const& target) const; + PLAPI std::shared_ptr> get(UUIDs const& target) const; - PLAPI bool set(UUID const target, int const val); + PLAPI bool set(UUIDs const target, int const val); - PLAPI bool del(UUID const& target); + PLAPI bool del(UUIDs const& target); PLAPI bool transfer(Player& target); diff --git a/include/plotcraft/PlotPos.h b/include/plotcraft/PlotPos.h deleted file mode 100644 index 0c4671d..0000000 --- a/include/plotcraft/PlotPos.h +++ /dev/null @@ -1,49 +0,0 @@ -#pragma once -#include "mc/math/Vec3.h" -#include "plotcraft/Config.h" -#include "plotcraft/Macro.h" - -namespace plo { - -// Disable C4244 -#pragma warning(disable : 4244) - -class PlotPos { -public: - int x, z; // 地皮坐标 - Vec3 minPos; // 地皮小端坐标 - Vec3 maxPos; // 地皮大端坐标 - bool mIsValid{true}; // 地皮是否有效 - - PLAPI PlotPos(); - PLAPI PlotPos(int x, int z); - PLAPI PlotPos(const Vec3& vec3); - - PLAPI bool isValid() const; - - PLAPI Vec3 getMin() const; - - PLAPI Vec3 getMax() const; - - PLAPI string toString() const; - - PLAPI string getPlotID() const; - - PLAPI string toDebug() const; - - PLAPI bool isPosInPlot(const Vec3& vec3) const; - - PLAPI Vec3 getSafestPos() const; - - PLAPI void tryFixMinAndMaxPos(); - - PLAPI bool isPosOnBorder(const Vec3& vec3); - - PLAPI std::vector getAdjacentPlots() const; - - PLAPI bool operator==(PlotPos const& other) const; - PLAPI bool operator!=(PlotPos const& other) const; -}; - - -} // namespace plo \ No newline at end of file diff --git a/include/plotcraft/Version.h b/include/plotcraft/Version.h new file mode 100644 index 0000000..5f6ebdb --- /dev/null +++ b/include/plotcraft/Version.h @@ -0,0 +1,9 @@ + +// Config +#define CONFIG_VERSION 7 + +// PlotMetadata +#define METADATA_VERSION 5 + +// PlayerSetting +#define SETTING_VERSION 1 diff --git a/include/plotcraft/core/PPos.h b/include/plotcraft/core/PPos.h new file mode 100644 index 0000000..3e3ea39 --- /dev/null +++ b/include/plotcraft/core/PPos.h @@ -0,0 +1,131 @@ +#pragma once +#include "mc/math/Vec3.h" +#include "mc/world/level/BlockPos.h" +#include "plotcraft/Config.h" +#include "plotcraft/Macro.h" + + +namespace plo { + +// TODO +// using DiagonPos = std::pair; +// enum class PlotDirection : int { +// North = 0, // 北 +// East = 1, // 东 +// South = 2, // 南 +// West = 3 // 西 +// }; +// class PlotRoadPos { +// public: +// int mX, mZ; +// DiagonPos mDiagonPos; +// bool mIsMergedPlot; // 是否是合并的地皮 +// PLAPI PlotRoadPos(); +// PLAPI PlotRoadPos(int x, int z); +// PLAPI PlotRoadPos(const Vec3& vec3); +// PLAPI PlotRoadPos(int x, int z, const DiagonPos& diagonPos, bool isMergedPlot); +// PLAPI string toString() const; +// PLAPI string getRoadID() const; +// PLAPI bool fillRoad(Block& block, bool includeBorder = false); +// PLAPI std::vector getAdjacentPlots() const; +// PLAPI static bool isAdjacent(const PlotRoadPos& road1, const PlotRoadPos& road2); +// }; +// class PlotCrossPos { +// public: +// int mX, mZ; +// DiagonPos mDiagonPos; +// bool mIsMergedPlot; // 是否是合并的地皮 +// PLAPI PlotCrossPos(); +// PLAPI PlotCrossPos(int x, int z); +// PLAPI PlotCrossPos(const Vec3& vec3); +// PLAPI PlotCrossPos(int x, int z, const DiagonPos& diagonPos, bool isMergedPlot); +// PLAPI string toString() const; +// PLAPI string getCrossID() const; +// PLAPI bool fillCross(Block& block); +// PLAPI std::vector getAdjacentRoads() const; +// }; + +using Vertexs = std::vector; // 顶点 + + +class PPos { +public: + int mX, mZ; // 地皮坐标 + Vertexs mVertexs; // 地皮顶点 + + PLAPI PPos(); + PLAPI PPos(int x, int z); + PLAPI PPos(const Vec3& vec3); + PLAPI PPos(int x, int z, Vertexs const& vertexs); + + PLAPI bool isValid() const; + + PLAPI string toString() const; + + PLAPI string getPlotID() const; + + PLAPI int getSurfaceY() const; + + PLAPI Vec3 getSafestPos() const; + + PLAPI bool isPosInPlot(const Vec3& vec3) const; + + PLAPI bool isPosOnBorder(const Vec3& vec3) const; + PLAPI bool isCubeOnBorder(class Cube const& cube) const; + PLAPI bool isRadiusOnBorder(class Radius const& radius) const; + + // PLAPI bool canMerge(PPos& other) const; // TODO + // PLAPI bool checkAndFixVertexs(); // TODO + // PLAPI bool tryMergeAndFixVertexs(PPos& other); // TODO + + // PLAPI std::vector getRangedPlots() const; // TODO: 获取范围内的地皮 + // PLAPI std::vector getRangedRoads() const; // TODO: 获取范围内的道路 + // PLAPI std::vector getRangedCrosses() const; // TODO: 获取范围内的路口 + + PLAPI bool operator==(PPos const& other) const; + PLAPI bool operator!=(PPos const& other) const; + + // static + PLAPI static bool isAdjacent(PPos const& plot1, PPos const& plot2); + + PLAPI static bool isPointInPolygon(const Vec3& point, Vertexs const& polygon); +}; + + +class Cube { +public: + BlockPos mMin, mMax; + + Cube() = delete; + Cube(BlockPos const& min, BlockPos const& max); + + Vertexs get2DVertexs() const; // 获取2D顶点 + + bool hasPos(BlockPos const& pos) const; // 判断一个点是否在立方体内 + + std::vector getRangedPlots() const; // 获取范围内的地皮 + + bool operator==(const Cube& other) const; + bool operator!=(const Cube& other) const; + + // static + static bool isCollision(Cube const& cube1, Cube const& cube2); // 判断两个立方体是否碰撞 +}; + + +class Radius { +public: + BlockPos mCenter; + int mRadius; + + Radius() = delete; + Radius(BlockPos const& center, int radius) : mCenter(center), mRadius(radius){}; + + std::vector getRangedPlots() const; // 获取范围内的地皮 + + bool operator==(const Radius& other) const; + bool operator!=(const Radius& other) const; +}; + + +} // namespace plo \ No newline at end of file diff --git a/include/plotcraft/core/Utils.h b/include/plotcraft/core/Utils.h new file mode 100644 index 0000000..7c45126 --- /dev/null +++ b/include/plotcraft/core/Utils.h @@ -0,0 +1,9 @@ +#pragma once + +namespace plo::core { + + +int getPlotDimensionId(); + + +} // namespace plo::core diff --git a/include/plotcraft/data/PlayerNameDB.h b/include/plotcraft/data/PlayerNameDB.h index 9aac3ee..6da202d 100644 --- a/include/plotcraft/data/PlayerNameDB.h +++ b/include/plotcraft/data/PlayerNameDB.h @@ -14,7 +14,6 @@ namespace plo::data { class PlayerNameDB { private: std::unique_ptr mPlayerNameDB; - bool isInit = false; PlayerNameDB() = default; PlayerNameDB(const PlayerNameDB&) = delete; @@ -25,10 +24,10 @@ class PlayerNameDB { PLAPI bool initPlayerNameDB(); PLAPI bool hasPlayer(string const& realName); - PLAPI bool hasPlayer(UUID_ const& uuid); // mce::UUID + PLAPI bool hasPlayer(UUIDm const& uuid); // mce::UUID - PLAPI string getPlayerName(UUID const& uuid); - PLAPI UUID getPlayerUUID(string const& realName); + PLAPI string getPlayerName(UUIDs const& uuid); + PLAPI UUIDs getPlayerUUID(string const& realName); PLAPI bool insertPlayer(Player& player); }; diff --git a/include/plotcraft/data/PlotBDStorage.h b/include/plotcraft/data/PlotBDStorage.h deleted file mode 100644 index 3ece142..0000000 --- a/include/plotcraft/data/PlotBDStorage.h +++ /dev/null @@ -1,86 +0,0 @@ -#pragma once -#include "ll/api/data/KeyValueDB.h" -#include "plotcraft/Macro.h" -#include "plotcraft/data/PlotMetadata.h" -#include -#include -#include -#include -#include - -using string = std::string; - -class PlotPos; - -namespace plo::data { - -struct PlayerSettingItem { - int version = 1; - - bool showPlotTip; // 是否显示地皮提示 -}; - - -class PlotBDStorage { -private: - std::unique_ptr mDB; - - // Cache: - std::vector mAdmins; // 管理 - std::unordered_map mPlots; // 地皮 - std::unordered_map mPlayerSettings; // 玩家设置 - -public: - PlotBDStorage() = default; - PlotBDStorage(const PlotBDStorage&) = delete; - PlotBDStorage& operator=(const PlotBDStorage&) = delete; - - PLAPI ll::data::KeyValueDB& getDB(); - PLAPI static PlotBDStorage& getInstance(); - - PLAPI void load(); - PLAPI void save(); // 保存所有数据 - PLAPI void save(PlotMetadata const& plot); - - PLAPI void tryStartSaveThread(); // 启动自动保存线程 - - PLAPI void initKey(); - - PLAPI void tryConvertOldDB(); - - // Admins - PLAPI bool hasAdmin(const UUID& uuid) const; - PLAPI bool isAdmin(const UUID& uuid) const; - PLAPI bool addAdmin(const UUID& uuid); - PLAPI bool delAdmin(const UUID& uuid); - PLAPI std::vector getAdmins() const; - - // Plots - PLAPI bool hasPlot(const PlotID& id) const; - - PLAPI bool delPlot(const PlotID& id); - - PLAPI bool addPlot(PlotID const& id, UUID const& owner, int x, int z); - PLAPI bool addPlot(PlotMetadataPtr plot); - - PLAPI PlotMetadataPtr getPlot(PlotID const& id) const; - PLAPI std::vector getPlots() const; - PLAPI std::vector getPlots(UUID const& owner) const; - - // Player settings - PLAPI bool hasPlayerSetting(const UUID& uuid) const; - PLAPI bool initPlayerSetting(const UUID& uuid); - PLAPI bool setPlayerSetting(const UUID& uuid, const PlayerSettingItem& setting); - PLAPI PlayerSettingItem getPlayerSetting(const UUID& uuid) const; - - // 辅助API - PLAPI std::vector getSaleingPlots() const; // 出售中的地皮 - - PLAPI bool buyPlotFromSale(PlotID const& pid, UUID const& buyer, bool resetShares = true); // 购买出售中的地皮 - - PLAPI PlotPermission - getPlayerPermission(UUID const& uuid, PlotID const& pid, bool ignoreAdmin = false) const; // 获取玩家的权限 -}; - - -} // namespace plo::data \ No newline at end of file diff --git a/include/plotcraft/data/PlotDBStorage.h b/include/plotcraft/data/PlotDBStorage.h new file mode 100644 index 0000000..45a1916 --- /dev/null +++ b/include/plotcraft/data/PlotDBStorage.h @@ -0,0 +1,85 @@ +#pragma once +#include "ll/api/data/KeyValueDB.h" +#include "plotcraft/Macro.h" +#include "plotcraft/Version.h" +#include "plotcraft/core/PPos.h" +#include "plotcraft/data/PlotMetadata.h" +#include +#include +#include +#include +#include + + +using string = std::string; + +namespace plo::data { + +struct PlayerSettingItem { + int version = SETTING_VERSION; + + bool showPlotTip; // 是否显示地皮提示 +}; + + +class PlotDBStorage { +private: + std::unique_ptr mDB; + + // Cache: + std::vector mAdmins; // 管理 + std::unordered_map mPlots; // 地皮 + std::unordered_map mPlayerSettings; // 玩家设置 + +public: + PlotDBStorage() = default; + PlotDBStorage(const PlotDBStorage&) = delete; + PlotDBStorage& operator=(const PlotDBStorage&) = delete; + + PLAPI ll::data::KeyValueDB& getDB(); + PLAPI static PlotDBStorage& getInstance(); + + PLAPI void load(); + PLAPI void save(); // 保存所有数据 + PLAPI void save(PlotMetadata const& plot); + + PLAPI void tryStartSaveThread(); // 启动自动保存线程 + + PLAPI void _initKey(); + + // Admins + PLAPI bool hasAdmin(const UUIDs& uuid) const; + PLAPI bool isAdmin(const UUIDs& uuid) const; + PLAPI bool addAdmin(const UUIDs& uuid); + PLAPI bool delAdmin(const UUIDs& uuid); + PLAPI std::vector getAdmins() const; + + // Plots + PLAPI bool hasPlot(const PlotID& id) const; + + PLAPI bool delPlot(const PlotID& id); + + PLAPI bool addPlot(PlotID const& id, UUIDs const& owner, int x, int z); + PLAPI bool addPlot(PlotMetadataPtr plot); + + PLAPI PlotMetadataPtr getPlot(PlotID const& id) const; + PLAPI std::vector getPlots() const; + PLAPI std::vector getPlots(UUIDs const& owner) const; + + // Player settings + PLAPI bool hasPlayerSetting(const UUIDs& uuid) const; + PLAPI bool initPlayerSetting(const UUIDs& uuid); + PLAPI bool setPlayerSetting(const UUIDs& uuid, const PlayerSettingItem& setting); + PLAPI PlayerSettingItem getPlayerSetting(const UUIDs& uuid) const; + + // 辅助API + PLAPI std::vector getSaleingPlots() const; // 出售中的地皮 + + PLAPI bool buyPlotFromSale(PlotID const& pid, UUIDs const& buyer, bool resetShares = true); // 购买出售中的地皮 + + PLAPI PlotPermission + getPlayerPermission(UUIDs const& uuid, PlotID const& pid, bool ignoreAdmin = false) const; // 获取玩家的权限 +}; + + +} // namespace plo::data \ No newline at end of file diff --git a/include/plotcraft/data/PlotMetadata.h b/include/plotcraft/data/PlotMetadata.h index e1c159b..2e12072 100644 --- a/include/plotcraft/data/PlotMetadata.h +++ b/include/plotcraft/data/PlotMetadata.h @@ -1,6 +1,7 @@ #pragma once #include "mc/deps/core/mce/UUID.h" #include "plotcraft/Macro.h" +#include "plotcraft/Version.h" #include #include #include @@ -14,9 +15,9 @@ using string = std::string; namespace plo::data { -typedef string PlotID; // PlotPos::getPlotID() -typedef mce::UUID UUID_; -typedef string UUID; +typedef string PlotID; // PPos::getPlotID() +typedef mce::UUID UUIDm; +typedef string UUIDs; typedef int CommentID; @@ -24,36 +25,89 @@ enum class PlotPermission : int { None = 0, Shared = 1, Owner = 2, Admin = 3 }; struct PlotCommentItem { CommentID mCommentID; - UUID mCommentPlayer; + UUIDs mCommentPlayer; string mCommentTime; string mContent; }; struct PlotShareItem { - UUID mSharedPlayer; + UUIDs mSharedPlayer; string mSharedTime; }; -#define METADATA_VERSION 3 struct PlotPermissionTable { - bool canDestroyBlock{false}; // 破坏方块 - bool canPlaceBlock{false}; // 放置方块 - bool canUseItemOn{true}; // 使用物品(右键) - bool canFireSpread{false}; // 火焰蔓延 - bool canAttack{true}; // 攻击 - bool canPickupItem{true}; // 拾取物品 - bool canInteractBlock{false}; // 与方块交互 - - // LSE - bool canFarmLandDecay{true}; // 耕地退化 - bool canOperateFrame{false}; // 操作展示框 - bool canMobHurt{false}; // 生物受伤 - bool canAttackBlock{false}; // 攻击方块 - bool canOperateArmorStand{false}; // 操作盔甲架 - bool canDropItem{true}; // 丢弃物品 - bool canStepOnPressurePlate{false}; // 踩压压力板 - bool canRide{false}; // 骑乘 - bool canWitherDestroyBlock{false}; // 凋零破坏方块 - bool canRedStoneUpdate{true}; // 红石更新 + // 标记 [x] 为复用权限 + bool allowFireSpread{true}; // 火焰蔓延 + bool allowAttackDragonEgg{false}; // 攻击龙蛋 + bool allowFarmDecay{true}; // 耕地退化 + bool allowPistonPush{true}; // 活塞推动 + bool allowRedstoneUpdate{true}; // 红石更新 + bool allowExplode{false}; // 爆炸 + bool allowDestroy{false}; // 允许破坏 + bool allowWitherDestroy{false}; // 允许凋零破坏 + bool allowPlace{false}; // 允许放置 [x] + bool allowAttackPlayer{false}; // 允许攻击玩家 + bool allowAttackAnimal{false}; // 允许攻击动物 + bool allowAttackMob{true}; // 允许攻击怪物 + bool allowOpenChest{false}; // 允许打开箱子 + bool allowPickupItem{false}; // 允许拾取物品 + bool allowThrowSnowball{true}; // 允许投掷雪球 + bool allowThrowEnderPearl{true}; // 允许投掷末影珍珠 + bool allowThrowEgg{true}; // 允许投掷鸡蛋 + bool allowThrowTrident{true}; // 允许投掷三叉戟 + bool allowDropItem{true}; // 允许丢弃物品 + bool allowShoot{false}; // 允许射击 [x] + bool allowThrowPotion{false}; // 允许投掷药水 [x] + bool allowRideEntity{false}; // 允许骑乘实体 + bool allowRideTrans{false}; // 允许骑乘矿车、船 + bool allowAxePeeled{false}; // 允许斧头去皮 + bool allowAttackEnderCrystal{false}; // 允许攻击末地水晶 + bool allowDestroyArmorStand{false}; // 允许破坏盔甲架 + + bool useAnvil{false}; // 使用铁砧 + bool useBarrel{false}; // 使用木桶 + bool useBeacon{false}; // 使用信标 + bool useBed{false}; // 使用床 + bool useBell{false}; // 使用钟 + bool useBlastFurnace{false}; // 使用高炉 + bool useBrewingStand{false}; // 使用酿造台 + bool useCampfire{false}; // 使用营火 + bool useFiregen{false}; // 使用打火石 + bool useCartographyTable{false}; // 使用制图台 + bool useComposter{false}; // 使用堆肥桶 + bool useCraftingTable{false}; // 使用工作台 + bool useDaylightDetector{false}; // 使用阳光探测器 + bool useDispenser{false}; // 使用发射器 + bool useDropper{false}; // 使用投掷器 + bool useEnchantingTable{false}; // 使用附魔台 + bool useDoor{false}; // 使用门 + bool useFenceGate{false}; // 使用栅栏门 + bool useFurnace{false}; // 使用熔炉 + bool useGrindstone{false}; // 使用砂轮 + bool useHopper{false}; // 使用漏斗 + bool useJukebox{false}; // 使用唱片机 + bool useLoom{false}; // 使用织布机 + bool useStonecutter{false}; // 使用切石机 + bool useNoteBlock{false}; // 使用音符盒 + bool useShulkerBox{false}; // 使用潜影盒 + bool useSmithingTable{false}; // 使用锻造台 + bool useSmoker{false}; // 使用烟熏炉 + bool useTrapdoor{false}; // 使用活板门 + bool useLectern{false}; // 使用讲台 + bool useCauldron{false}; // 使用炼药锅 + bool useLever{false}; // 使用拉杆 + bool useButton{false}; // 使用按钮 + bool useRespawnAnchor{false}; // 使用重生锚 + bool useItemFrame{false}; // 使用物品展示框 + bool useFishingHook{false}; // 使用钓鱼竿 + bool useBucket{false}; // 使用桶 + bool usePressurePlate{false}; // 使用压力板 + bool useArmorStand{false}; // 使用盔甲架 + bool useBoneMeal{false}; // 使用骨粉 + bool useHoe{false}; // 使用锄头 + bool useShovel{false}; // 使用锹 + + bool editFlowerPot{false}; // 编辑花盆 + bool editSign{false}; // 编辑告示牌 }; @@ -64,30 +118,28 @@ class PlotMetadata { // private: PlotID mPlotID; - string mPlotName = ""; - UUID mPlotOwner = ""; + string mPlotName{""}; + UUIDs mPlotOwner{""}; int mPlotX; int mPlotZ; - bool mIsSale = false; // 是否出售 - int mPrice = 0; // 出售价格 + bool mIsSale{false}; // 是否出售 + int mPrice{0}; // 出售价格 - PlotPermissionTable mPermissionTable; // 权限表 - - std::vector mSharedPlayers; // 共享者列表 - - std::vector mComments; // 评论列表 + PlotPermissionTable mPermissionTable; // 权限表 + std::vector mSharedPlayers; // 共享者列表 + std::vector mComments; // 评论列表 public: // Constructors: PLAPI static PlotMetadataPtr make(); PLAPI static PlotMetadataPtr make(PlotID const& plotID, int x, int z); - PLAPI static PlotMetadataPtr make(PlotID const& plotID, UUID const& owner, int x, int z); - PLAPI static PlotMetadataPtr make(PlotID const& plotID, UUID const& owner, string const& name, int x, int z); + PLAPI static PlotMetadataPtr make(PlotID const& plotID, UUIDs const& owner, int x, int z); + PLAPI static PlotMetadataPtr make(PlotID const& plotID, UUIDs const& owner, string const& name, int x, int z); // APIs: - PLAPI bool isOwner(UUID const& uuid) const; + PLAPI bool isOwner(UUIDs const& uuid) const; PLAPI bool setPlotName(string const& name); @@ -97,13 +149,13 @@ class PlotMetadata { PLAPI bool setZ(int z); - PLAPI bool setPlotOwner(UUID const& owner); + PLAPI bool setPlotOwner(UUIDs const& owner); - PLAPI bool isSharedPlayer(UUID const& uuid) const; + PLAPI bool isSharedPlayer(UUIDs const& uuid) const; - PLAPI bool addSharedPlayer(UUID const& uuid); + PLAPI bool addSharedPlayer(UUIDs const& uuid); - PLAPI bool delSharedPlayer(UUID const& uuid); + PLAPI bool delSharedPlayer(UUIDs const& uuid); PLAPI bool resetSharedPlayers(); @@ -111,9 +163,9 @@ class PlotMetadata { PLAPI bool hasComment(CommentID const& commentID) const; - PLAPI bool isCommentOwner(CommentID const& commentID, UUID const& uuid) const; + PLAPI bool isCommentOwner(CommentID const& commentID, UUIDs const& uuid) const; - PLAPI bool addComment(UUID const& uuid, string const& content); + PLAPI bool addComment(UUIDs const& uuid, string const& content); PLAPI bool delComment(CommentID const& commentID); @@ -123,7 +175,7 @@ class PlotMetadata { PLAPI std::optional getComment(CommentID const& commentID) const; PLAPI std::vector getComments() const; - PLAPI std::vector getComments(UUID const& uuid) const; + PLAPI std::vector getComments(UUIDs const& uuid) const; PLAPI bool setSaleStatus(bool isSale); @@ -139,13 +191,13 @@ class PlotMetadata { PLAPI string getPlotName() const; - PLAPI UUID getPlotOwner() const; + PLAPI UUIDs getPlotOwner() const; PLAPI int getX() const; PLAPI int getZ() const; - PLAPI PlotPermission getPlayerInThisPlotPermission(UUID const& uuid) const; // 玩家在此地皮的权限 + PLAPI PlotPermission getPlayerInThisPlotPermission(UUIDs const& uuid) const; // 玩家在此地皮的权限 PLAPI PlotPermissionTable& getPermissionTable(); PLAPI PlotPermissionTable const& getPermissionTableConst() const; diff --git a/include/plotcraft/event/PlotEvents.h b/include/plotcraft/event/PlotEvents.h index 4323047..9b6c890 100644 --- a/include/plotcraft/event/PlotEvents.h +++ b/include/plotcraft/event/PlotEvents.h @@ -3,7 +3,7 @@ #include "ll/api/event/Event.h" #include "mc/world/actor/player/Player.h" #include "plotcraft/Macro.h" -#include "plotcraft/PlotPos.h" +#include "plotcraft/core/PPos.h" #include "plotcraft/data/PlotMetadata.h" namespace plo::event { @@ -13,28 +13,28 @@ using namespace data; // 玩家进入地皮 class PlayerEnterPlot final : public ll::event::Event { private: - PlotPos const& mPos; // 地皮坐标 - Player* mPlayer; // 玩家指针 + PPos const& mPos; // 地皮坐标 + Player* mPlayer; // 玩家指针 public: - constexpr explicit PlayerEnterPlot(PlotPos const& pos, Player* player) : mPos(pos), mPlayer(player) {} + constexpr explicit PlayerEnterPlot(PPos const& pos, Player* player) : mPos(pos), mPlayer(player) {} - PLAPI Player* getPlayer() const; - PLAPI PlotPos const& getPos() const; + PLAPI Player* getPlayer() const; + PLAPI PPos const& getPos() const; }; // 玩家离开地皮 class PlayerLeavePlot final : public ll::event::Event { private: - PlotPos const& mPos; // 地皮坐标 - Player* mPlayer; // 玩家指针 + PPos const& mPos; // 地皮坐标 + Player* mPlayer; // 玩家指针 public: - constexpr explicit PlayerLeavePlot(const PlotPos& pos, Player* player) : mPos(pos), mPlayer(player) {} + constexpr explicit PlayerLeavePlot(const PPos& pos, Player* player) : mPos(pos), mPlayer(player) {} - PLAPI Player* getPlayer() const; - PLAPI PlotPos const& getPos() const; + PLAPI Player* getPlayer() const; + PLAPI PPos const& getPos() const; }; diff --git a/include/plotcraft/utils/Area.h b/include/plotcraft/utils/Area.h index 1f61215..8e1a197 100644 --- a/include/plotcraft/utils/Area.h +++ b/include/plotcraft/utils/Area.h @@ -288,7 +288,12 @@ PLAPI inline FindResult findSafePos( try { if (startY <= stopY) return result; // 起始高度大于或等于停止高度,直接返回 - BlockPos bp(findX, startY, findZ); + BlockPos bp(findX, startY, findZ); + static const auto getBlock = [&](int y) { + auto bp2 = bp; + bp2.y = y; + return mc::getBlock(bp2, findDimid); + }; int currentTraversalY = startY; // 当前遍历的y值 while (currentTraversalY > stopY) { @@ -302,9 +307,9 @@ PLAPI inline FindResult findSafePos( } else if (currentTraversalY <= stopY || utils::some(dangerousBlocks, bl.getTypeName())) { break; } else if ( - !bl.isAir() && // 落脚方块 - mc::getBlock(currentTraversalY + offset1, bp, findDimid).isAir() // 玩家身体 下半 - && mc::getBlock(currentTraversalY + offset2, bp, findDimid).isAir() // 玩家身体 上半 + !bl.isAir() && // 落脚方块 + getBlock(currentTraversalY + offset1).isAir() // 玩家身体 下半 + && getBlock(currentTraversalY + offset2).isAir() // 玩家身体 上半 ) { // 安全位置 落脚点安全、上两格是空气 result.y = currentTraversalY + 1; // 往上跳一格 diff --git a/include/plotcraft/utils/EconomySystem.h b/include/plotcraft/utils/EconomySystem.h new file mode 100644 index 0000000..1200a29 --- /dev/null +++ b/include/plotcraft/utils/EconomySystem.h @@ -0,0 +1,45 @@ +#pragma once +#include "Date.h" +#include "Utils.h" +#include "mc/world/actor/player/Player.h" +#include "plotcraft/Macro.h" + +using string = std::string; + +namespace plo::utils { + + +enum class EconomyType : int { Unknown = -1, LegacyMoney = 0, ScoreBoard = 1 }; +struct EconomyConfig { + bool enable = false; + EconomyType type = EconomyType::LegacyMoney; + string scoreName = ""; + string economicName = "金币"; +}; + +class EconomySystem { +public: + EconomyConfig mEconomyConfig; + + EconomySystem() = default; + EconomySystem(const EconomySystem&) = delete; + EconomySystem& operator=(const EconomySystem&) = delete; + + PLAPI static EconomySystem& getInstance(); + PLAPI bool updateConfig(EconomyConfig config); + + PLAPI long long get(Player& player); + + PLAPI bool set(Player& player, long long money); + + PLAPI bool add(Player& player, long long money); + + PLAPI bool reduce(Player& player, long long money); + + PLAPI string getMoneySpendTipStr(Player& player, long long money); + + PLAPI void sendLackMoneyTip(Player& player, long long money); // 发送经济不足提示 +}; + + +} // namespace plo::utils diff --git a/include/plotcraft/utils/Mc.h b/include/plotcraft/utils/Mc.h index d7d0481..c11b03b 100644 --- a/include/plotcraft/utils/Mc.h +++ b/include/plotcraft/utils/Mc.h @@ -62,10 +62,6 @@ namespace plo::mc { PLAPI inline Block const& getBlock(BlockPos& bp, int dimid) { return ll::service::getLevel()->getDimension(dimid)->getBlockSourceFromMainChunkSource().getBlock(bp); } -PLAPI inline Block const& getBlock(int y, BlockPos bp, int dimid) { - bp.y = y; - return getBlock(bp, dimid); -} PLAPI inline void executeCommand(const string& cmd, Player* player = nullptr) { if (player) { @@ -143,4 +139,56 @@ PLAPI inline BlockPos face2Pos(BlockPos const& sour, uchar face) { } +// Template function sendText, usage: sendText() or sendText(). +enum class LogLevel : int { Normal = -1, Debug = 0, Info = 1, Warn = 2, Error = 3, Fatal = 4, Success = 5 }; +inline static std::unordered_map Color = { + {LogLevel::Normal, "§b"}, + {LogLevel::Debug, "§7"}, + {LogLevel::Info, "§r"}, + {LogLevel::Warn, "§e"}, + {LogLevel::Error, "§c"}, + {LogLevel::Fatal, "§4"}, + {LogLevel::Success, "§a"} +}; + +template +PLAPI inline string format(const string& fmt, Args... args) { + try { + return fmt::vformat(fmt, fmt::make_format_args(args...)); + } catch (...) { + return fmt; + } +} + +template +PLAPI inline void sendText(Player& player, const string& fmt, Args&&... args) { + player.sendMessage(format(PLUGIN_TITLE + Color[type] + fmt, args...)); +} +template +PLAPI inline void sendText(CommandOutput& output, const string& fmt, Args&&... args) { + if constexpr (type == LogLevel::Error || type == LogLevel::Fatal) { + output.error(format(PLUGIN_TITLE + Color[type] + fmt, args...)); + } else { + output.success(format(PLUGIN_TITLE + Color[type] + fmt, args...)); + } +} +template +PLAPI inline void sendText(Player* player, const string& fmt, Args&&... args) { + if (player) { + return sendText(*player, fmt, args...); + } else { + std::runtime_error("Failed in sendText: player is nullptr"); + } +} +template +PLAPI inline void sendText(const string& realName, const string& fmt, Args&&... args) { + auto level = ll::service::getLevel(); + if (level.has_value()) { + return sendText(level->getPlayer(realName), fmt, args...); + } else { + std::runtime_error("Failed in sendText: level is nullptr"); + } +} + + } // namespace plo::mc \ No newline at end of file diff --git a/include/plotcraft/utils/Menu.h b/include/plotcraft/utils/Menu.h deleted file mode 100644 index af48624..0000000 --- a/include/plotcraft/utils/Menu.h +++ /dev/null @@ -1,44 +0,0 @@ -#pragma once -#include "mc/world/actor/player/Player.h" -#include "nlohmann/json.hpp" -#include "nlohmann/json_fwd.hpp" -#include "plotcraft/Macro.h" -#include -#include -#include -#include -#include - - -using string = std::string; -using json = nlohmann::json; -using path = std::filesystem::path; - - -namespace plo::utils { - -struct Menu { - struct ButtonItem { - string title = ""; // 按钮标题 - string imageData = ""; // 按钮图片 url 或 path - string imageType = ""; // 图片类型,url 或 path - string callbackType = ""; // 按钮回调类型 - string callbackRun = ""; // 按钮回调函数 - }; - - string title = PLUGIN_NAME; - string content = ""; - std::vector buttons = {}; - - PLAPI void sendTo(Player& player); - PLAPI static std::unique_ptr fromJSON(const json& json); - PLAPI static std::unique_ptr fromJsonFile(const string path); - PLAPI static void fromJsonFile(Player& player); - - // static members - PLAPI static path rootDir; - PLAPI static std::unordered_map> functions; - PLAPI static bool hasFunction(const string name); -}; - -} // namespace plo::utils \ No newline at end of file diff --git a/include/plotcraft/utils/Moneys.h b/include/plotcraft/utils/Moneys.h deleted file mode 100644 index ae5e934..0000000 --- a/include/plotcraft/utils/Moneys.h +++ /dev/null @@ -1,99 +0,0 @@ -#pragma once -#include "Date.h" -#include "Utils.h" -#include "mc/deps/core/mce/UUID.h" -#include "mc/world/actor/player/Player.h" -#include "plotcraft/Macro.h" - -using string = std::string; - -namespace plo::utils { - -enum class MoneysType : int { Unknown = -1, LLMoney = 0, ScoreBoard = 1 }; - -struct MoneysConfig { - bool Enable = false; - MoneysType MoneyType = MoneysType::LLMoney; - string MoneyName = "money"; - string ScoreName = ""; -}; - -class ScoreBoardMoney { -private: - string mScoreName; - -public: - PLAPI ScoreBoardMoney(const string& scoreName = ""); - - PLAPI void setScoreName(const string& scoreName); - - PLAPI int getScore(Player* player); - PLAPI int getScore(Player& player); - PLAPI int getScore(mce::UUID uuid); - PLAPI int getScore(const string& realName); - - PLAPI bool setScore(Player* player, int score); - PLAPI bool setScore(Player& player, int score); - PLAPI bool setScore(mce::UUID uuid, int score); - PLAPI bool setScore(const string& realName, int score); - - PLAPI bool addScore(Player* player, int score); - PLAPI bool addScore(Player& player, int score); - PLAPI bool addScore(mce::UUID uuid, int score); - PLAPI bool addScore(const string& realName, int score); - - PLAPI bool reduceScore(Player* player, int score); - PLAPI bool reduceScore(Player& player, int score); - PLAPI bool reduceScore(mce::UUID uuid, int score); - PLAPI bool reduceScore(const string& realName, int score); -}; - - -// 继承ScoreBoardMoney -class Moneys : public ScoreBoardMoney { -private: - bool mIsEnable = false; - MoneysType mType = MoneysType::Unknown; // 经济类型 - string mMoneyName; // 货币名称 - - Moneys() = default; - Moneys(const Moneys&) = delete; - Moneys& operator=(const Moneys&) = delete; - - PLAPI void throwUnknownType(Player* player = nullptr); - -public: - PLAPI static Moneys& getInstance(); - // 单例模式,服务器启动后请调用此方法初始化 - PLAPI bool updateConfig(MoneysConfig config); - - PLAPI long long getMoney(Player& player); - PLAPI long long getMoney(Player* player); - PLAPI long long getMoney(mce::UUID uuid); - PLAPI long long getMoney(const string& realName); - - PLAPI bool setMoney(Player& player, long long money); - PLAPI bool setMoney(Player* player, long long money); - PLAPI bool setMoney(mce::UUID uuid, long long money); - PLAPI bool setMoney(const string& realName, long long money); - - PLAPI bool addMoney(Player& player, long long money); - PLAPI bool addMoney(Player* player, long long money); - PLAPI bool addMoney(mce::UUID uuid, long long money); - PLAPI bool addMoney(const string& realName, long long money); - - // ! 此 API 已封装经济不足提示,无需手动发送 - PLAPI bool reduceMoney(Player& player, long long money); - PLAPI bool reduceMoney(Player* player, long long money); - PLAPI bool reduceMoney(mce::UUID uuid, long long money); - PLAPI bool reduceMoney(const string& realName, long long money); - - PLAPI string getMoneySpendTipStr(Player& player, long long money); - PLAPI string getMoneySpendTipStr(Player* player, long long money); - PLAPI string getMoneySpendTipStr(mce::UUID uuid, long long money); - PLAPI string getMoneySpendTipStr(const string& realName, long long money); - - PLAPI void sendMoneySpendTip(Player& player, long long money); // 发送经济不足提示 -}; - -} // namespace plo::utils diff --git a/include/plotcraft/utils/Text.h b/include/plotcraft/utils/Text.h deleted file mode 100644 index 9b90a11..0000000 --- a/include/plotcraft/utils/Text.h +++ /dev/null @@ -1,81 +0,0 @@ -#pragma once -#include "Utils.h" -#include "ll/api/Logger.h" -#include "ll/api/service/Bedrock.h" -#include "mc/world/actor/player/Player.h" -#include "mc/world/level/Level.h" -#include "plotcraft/Macro.h" -#include -#include -#include -#include - -using string = std::string; - -namespace plo::utils { - -enum class Level : int { Normal = -1, Debug = 0, Info = 1, Warn = 2, Error = 3, Fatal = 4, Success = 5 }; - -inline static std::unordered_map Color = { - {Level::Normal, "§b"}, // aqua - {Level::Debug, "§7"}, // gray - {Level::Info, "§r"}, // default - {Level::Warn, "§e"}, // yellow - {Level::Error, "§c"}, // red - {Level::Fatal, "§4"}, // dark_red - {Level::Success, "§a"} // green -}; - -template -PLAPI inline string format(const string& fmt, Args... args) { - try { - return fmt::vformat(fmt, fmt::make_format_args(args...)); - } catch (...) { - return fmt; - } -} - -// Template function sendText, usage: sendText() or sendText(). -template -PLAPI inline void sendText(Player& player, const string& fmt, Args&&... args) { - player.sendMessage(format(PLUGIN_TITLE + Color[type] + fmt, args...)); -} -template -PLAPI inline void sendText(CommandOutput& output, const string& fmt, Args&&... args) { - if constexpr (type == Level::Error || type == Level::Fatal) { - output.error(format(PLUGIN_TITLE + Color[type] + fmt, args...)); - } else { - output.success(format(PLUGIN_TITLE + Color[type] + fmt, args...)); - } -} -template -PLAPI inline void sendText(Player* player, const string& fmt, Args&&... args) { - if (player) { - return sendText(*player, fmt, args...); - } else { - std::runtime_error("Failed in sendText: player is nullptr"); - } -} -template -PLAPI inline void sendText(const string& realName, const string& fmt, Args&&... args) { - auto level = ll::service::getLevel(); - if (level.has_value()) { - return sendText(level->getPlayer(realName), fmt, args...); - } else { - std::runtime_error("Failed in sendText: level is nullptr"); - } -} -template -PLAPI inline void sendText(ll::Logger& logger, const string& fmt, Args&&... args) { - if constexpr (type == Level::Error) { - logger.error(format(PLUGIN_TITLE + Color[type] + fmt, args...)); - } else if constexpr (type == Level::Fatal) { - logger.fatal(format(PLUGIN_TITLE + Color[type] + fmt, args...)); - } else if constexpr (type == Level::Warn) { - logger.warn(format(PLUGIN_TITLE + Color[type] + fmt, args...)); - } else { - logger.info(format(PLUGIN_TITLE + Color[type] + fmt, args...)); - } -} - -} // namespace plo::utils \ No newline at end of file diff --git a/include/plotcraft/utils/Utils.h b/include/plotcraft/utils/Utils.h index f75cd25..8aec1cd 100644 --- a/include/plotcraft/utils/Utils.h +++ b/include/plotcraft/utils/Utils.h @@ -30,25 +30,6 @@ PLAPI inline bool some(std::vector const& vec, T const& it) { return std::find(vec.begin(), vec.end(), it) != vec.end(); } - -PLAPI inline string toJson(const std::unordered_map& map) { - string json = "{"; - for (const auto& pair : map) { - json += "\"" + pair.first + "\":" + std::to_string(pair.second) + ","; - } - json[json.size() - 1] = '}'; - return json; -} - -PLAPI inline string toJson(const std::unordered_map& map) { - string json = "{"; - for (const auto& pair : map) { - json += "\"" + pair.first + "\":" + std::to_string(pair.second) + ","; - } - json[json.size() - 1] = '}'; - return json; -} - PLAPI inline void DebugFormPrint(const ll::form::CustomFormResult& dt) { #ifdef DEBUG std::cout << "\033[0m\033[1;35m" diff --git a/scripts/after_build.lua b/scripts/after_build.lua index 45cc3d4..c82414c 100644 --- a/scripts/after_build.lua +++ b/scripts/after_build.lua @@ -105,14 +105,6 @@ function pack_plugin(target,plugin_define) local langdir = path.join(os.projectdir(), "assets", "lang") os.cp(langdir, outputdir) - local srcDir = path.join(os.projectdir(), "src") - local remoteDir = path.join(srcDir, "remote") - local libDir = path.join(remoteDir, "lib") - os.cp(libDir, outputdir) - - local lseDir = path.join(remoteDir, "lse") - os.cp(lseDir, outputdir) - formattedmanifest = string_formatter(manifest, plugin_define) io.writefile(manifestfile,formattedmanifest) cprint("${bright green}[Plugin Packer]: ${reset}plugin already generated to " .. outputdir) diff --git a/src/plotcraft/Config.cc b/src/plotcraft/Config.cc index cf6a09d..9ccbb5c 100644 --- a/src/plotcraft/Config.cc +++ b/src/plotcraft/Config.cc @@ -21,15 +21,18 @@ void loadConfig() { bool const ok = ll::config::loadConfig(cfg, path); - if (!ok) { - logger.warn("loadConfig 返回了预期之外的结果,尝试备份并覆写配置文件..."); + if (cfg.generator.subChunkNum <= 0 && cfg.generator.type == PlotGeneratorType::Default) { + cfg.generator.subChunkNum = 1; + logger.error("subChunkNum 不能小于等于0,已自动设置为1"); updateConfig(); } + if (cfg.generator.templateFile == "" && cfg.generator.type == PlotGeneratorType::Template) { + throw std::runtime_error("初始化失败,templateFile 不能为空!"); + } - if (cfg.generator.subChunkNum <= 0) { - cfg.generator.subChunkNum = 1; - logger.warn("subChunkNum 不能小于等于0,已自动设置为1"); + if (!ok) { + logger.warn("loadConfig 返回了预期之外的结果,尝试备份并覆写配置文件..."); updateConfig(); } } @@ -49,5 +52,13 @@ void updateConfig() { } } +// double calculateMergePlotPrice(int mergeCount) { +// if (mergeCount <= 0) { +// return cfg.plotWorld.baseMergePlotPrice; +// } +// double multiplier = std::max(1.0, cfg.plotWorld.mergePriceMultiplier); +// return cfg.plotWorld.baseMergePlotPrice * std::pow(multiplier, mergeCount); +// } + } // namespace plo::config \ No newline at end of file diff --git a/src/plotcraft/EconomyQueue.cc b/src/plotcraft/EconomyQueue.cc index 4468c20..f5f847a 100644 --- a/src/plotcraft/EconomyQueue.cc +++ b/src/plotcraft/EconomyQueue.cc @@ -9,12 +9,12 @@ EconomyQueue& EconomyQueue::getInstance() { return instance; } -bool EconomyQueue::has(UUID const& target) const { +bool EconomyQueue::has(UUIDs const& target) const { return std::find_if(mQueue->begin(), mQueue->end(), [&target](auto const& pair) { return pair->first == target; }) != mQueue->end(); } -std::shared_ptr> EconomyQueue::get(UUID const& target) const { +std::shared_ptr> EconomyQueue::get(UUIDs const& target) const { auto it = std::find_if(mQueue->begin(), mQueue->end(), [&target](auto const& pair) { return pair->first == target; }); if (it == mQueue->end()) { @@ -23,7 +23,7 @@ std::shared_ptr> EconomyQueue::get(UUID const& target) return *it; } -bool EconomyQueue::set(UUID const target, int const val) { +bool EconomyQueue::set(UUIDs const target, int const val) { auto it = std::find_if(mQueue->begin(), mQueue->end(), [&target](auto const& pair) { return pair->first == target; }); if (it != mQueue->end()) { @@ -31,14 +31,14 @@ bool EconomyQueue::set(UUID const target, int const val) { save(); return true; } else { - auto ptr = std::make_shared>(std::make_pair(target, val)); + auto ptr = std::make_shared>(std::make_pair(target, val)); mQueue->push_back(ptr); save(); return true; } } -bool EconomyQueue::del(UUID const& target) { +bool EconomyQueue::del(UUIDs const& target) { auto it = std::find_if(mQueue->begin(), mQueue->end(), [&target](auto const& pair) { return pair->first == target; }); if (it != mQueue->end()) { @@ -50,12 +50,12 @@ bool EconomyQueue::del(UUID const& target) { } bool EconomyQueue::transfer(Player& target) { - UUID uid = target.getUuid().asString(); + UUIDs uid = target.getUuid().asString(); if (!has(uid)) return false; - auto& ms = utils::Moneys::getInstance(); + auto& ms = utils::EconomySystem::getInstance(); auto ptr = get(uid); - bool const ok = ms.addMoney(target, ptr->second); + bool const ok = ms.add(target, ptr->second); if (ok) { del(uid); return true; @@ -67,7 +67,7 @@ bool EconomyQueue::save() { /* [ [ - "UUID", + "UUIDs", 000000000000 ] ] @@ -87,7 +87,7 @@ bool EconomyQueue::load() { throw std::runtime_error("EconomyQueue has already been initialized."); } mPath = my_plugin::MyPlugin::getInstance().getSelf().getDataDir() / "EconomyQueue.json"; - mQueue = std::make_shared>>>(); + mQueue = std::make_shared>>>(); if (!fs::exists(mPath)) { // create father directory if not exist @@ -103,8 +103,7 @@ bool EconomyQueue::load() { ifs >> j; ifs.close(); for (auto const& item : j) { - mQueue->push_back(std::make_shared>(std::make_pair(UUID(item[0]), item[1]) - )); + mQueue->push_back(std::make_shared>(std::make_pair(UUIDs(item[0]), item[1]))); } return true; } diff --git a/src/plotcraft/PlotPos.cc b/src/plotcraft/PlotPos.cc deleted file mode 100644 index b527924..0000000 --- a/src/plotcraft/PlotPos.cc +++ /dev/null @@ -1,171 +0,0 @@ -#include "plotcraft/PlotPos.h" -#include "fmt/core.h" -#include "fmt/format.h" -#include "plotcraft/Config.h" -#include - - -namespace plo { - -/* - # 生成器1: - 基准点:Vec3{0,0,0} - 地皮(0,0).minPos = Vec3{0,-64,0} - 地皮(0,0).maxPos = Vec3{0 + config::cfg.generator.plotWidth -1 ,320,0 + config::cfg.generator.plotWidth -1} - - # 生成器2: - 基准点:Vec3{0,0,0} - 地皮(0,0).minPos = Vec3{1,-64,1} - 地皮(0,0).maxPos = Vec3{chunk_n * 16 - 2 ,320, chunk_n * 16 - 2} // chunk_n为区块数量,-2为2格固定道路 - - 横向x轴,纵向z轴 - -1,1 0,1 1,1 - -1,0 0,0 1,0 - -1,-1 0,-1 1,-1 - */ - -PlotPos::PlotPos() : x(0), z(0), mIsValid(false) { - minPos = Vec3{0, 0, 0}; - maxPos = Vec3{0, 0, 0}; -} - -PlotPos::PlotPos(int x, int z) : x(x), z(z) { -#ifdef GEN_1 - // Generator 1 - auto& cfg = config::cfg.generator; - int totalSize = cfg.plotWidth + cfg.roadWidth; - minPos = Vec3{x * totalSize, -64, z * totalSize}; - maxPos = Vec3{minPos.x + cfg.plotWidth - 1, 320, minPos.z + cfg.plotWidth - 1}; -#endif - -#ifdef GEN_2 - // Generator 2 - auto& cfg = config::cfg.generator; - int totalSize = cfg.plotChunkSize * 16; // 地皮大小,含道路宽度 (3*16=48) - minPos = Vec3{x * totalSize + 1, -64, z * totalSize + 1}; - maxPos = Vec3{minPos.x + totalSize - 1, 320, minPos.z + totalSize - 1}; -#endif -} - -PlotPos::PlotPos(const Vec3& vec3) { -#ifdef GEN_1 - // Generator 1 - auto& cfg = config::cfg.generator; - int totalSize = cfg.plotWidth + cfg.roadWidth; - int gridX = std::floor(vec3.x / totalSize); - int gridZ = std::floor(vec3.z / totalSize); - int localX = static_cast(std::floor(vec3.x)) % totalSize; - int localZ = static_cast(std::floor(vec3.z)) % totalSize; - - if (localX < 0) localX += totalSize; - if (localZ < 0) localZ += totalSize; - - if (localX >= cfg.plotWidth || localZ >= cfg.plotWidth) { - // Point is on the road - minPos = Vec3{0, 0, 0}; - maxPos = Vec3{0, 0, 0}; - x = 0; - z = 0; - mIsValid = false; // 无效的地皮点 - } else { - x = gridX; - z = gridZ; - minPos = Vec3{x * totalSize, -64, z * totalSize}; - maxPos = Vec3{minPos.x + cfg.plotWidth - 1, 320, minPos.z + cfg.plotWidth - 1}; - } -#endif - -#ifdef GEN_2 - // Generator 2 - auto& cfg = config::cfg.generator; - int totalSize = cfg.plotChunkSize * 16; // 地皮大小 + 道路宽度 => (3*16=48) - int plotWidth = totalSize - 2; // 地皮大小 (48-2=46) - - int gridX = std::floor(vec3.x / totalSize); - int gridZ = std::floor(vec3.z / totalSize); - int localX = static_cast(std::floor(vec3.x)) % totalSize; // 46 % 48 = 46 => (当前坐标和完整大小取模) - int localZ = static_cast(std::floor(vec3.z)) % totalSize; - - if (localX < 1) localX += totalSize; // 0 + 1 = 1 => border - if (localZ < 1) localZ += totalSize; - - // 1 > 1 46 > 46 - if (localX > plotWidth || localZ > plotWidth) { - // Point is on the road - minPos = Vec3{0, 0, 0}; - maxPos = Vec3{0, 0, 0}; - x = 0; - z = 0; - mIsValid = false; // 无效的地皮点 - } else { - x = gridX; - z = gridZ; - minPos = Vec3{x * totalSize + 1, -64, z * totalSize + 1}; - maxPos = Vec3{minPos.x + plotWidth - 1, 320, minPos.z + plotWidth - 1}; - } -#endif -} - - -Vec3 PlotPos::getSafestPos() const { - auto& cfg = config::cfg.generator; - int y = -64 + (cfg.subChunkNum * 16) + 2; - return Vec3{minPos.x, y, minPos.z}; -} - -bool PlotPos::isValid() const { return mIsValid; } - -Vec3 PlotPos::getMin() const { return minPos; } - -Vec3 PlotPos::getMax() const { return maxPos; } - -string PlotPos::toString() const { return fmt::format("({0},{1})", x, z); } -string PlotPos::getPlotID() const { return toString(); } - -string PlotPos::toDebug() const { - return fmt::format("{0} | {1} => {2}", toString(), minPos.toString(), maxPos.toString()); -} - -bool PlotPos::isPosInPlot(const Vec3& vec3) const { - return vec3.x >= minPos.x && vec3.x <= maxPos.x && vec3.z >= minPos.z && vec3.z <= maxPos.z; -} - -std::vector PlotPos::getAdjacentPlots() const { - return {PlotPos(x - 1, z), PlotPos(x + 1, z), PlotPos(x, z - 1), PlotPos(x, z + 1)}; -} - - -bool PlotPos::isPosOnBorder(const Vec3& vec3) { - if (vec3.y < -64 || vec3.y > 320) { - return false; - } - - tryFixMinAndMaxPos(); - - // X 和 Z 坐标是否在边框上 - bool onXBorder = (vec3.x == minPos.x || vec3.x == maxPos.x); - bool onZBorder = (vec3.z == minPos.z || vec3.z == maxPos.z); - - // X 和 Z 坐标是否在地皮范围内 - bool withinXRange = (vec3.x >= minPos.x && vec3.x <= maxPos.x); - bool withinZRange = (vec3.z >= minPos.z && vec3.z <= maxPos.z); - - // 判断是否在边框上 - return (onXBorder && withinXRange) || (onZBorder && withinZRange); -} - - -void PlotPos::tryFixMinAndMaxPos() { - if (minPos.x > maxPos.x) std::swap(minPos.x, maxPos.x); - if (minPos.y > maxPos.y) std::swap(minPos.y, maxPos.y); - if (minPos.z > maxPos.z) std::swap(minPos.z, maxPos.z); -} - - -bool PlotPos::operator!=(PlotPos const& other) const { return !(*this == other); } -bool PlotPos::operator==(PlotPos const& other) const { - return other.x == x && other.z == z && other.mIsValid == mIsValid && other.minPos == minPos - && other.maxPos == maxPos; -} - -} // namespace plo \ No newline at end of file diff --git a/src/plotcraft/command/Command.cc b/src/plotcraft/command/Command.cc index dc5c062..c7463d2 100644 --- a/src/plotcraft/command/Command.cc +++ b/src/plotcraft/command/Command.cc @@ -13,19 +13,17 @@ #include "mc/world/level/chunk/LevelChunk.h" #include "mc/world/level/dimension/VanillaDimensions.h" #include "plotcraft/Config.h" +#include "plotcraft/core/Utils.h" #include "plotcraft/data/PlayerNameDB.h" -#include "plotcraft/data/PlotBDStorage.h" +#include "plotcraft/data/PlotDBStorage.h" #include "plotcraft/data/PlotMetadata.h" #include "plotcraft/gui/Global.h" -#include "plotcraft/utils/Area.h" -#include "plotcraft/utils/Text.h" +#include -#include "plotcraft/core/CoreUtils.h" - namespace plo::command { - using namespace plo::utils; +using namespace plo::mc; struct ParamOp { enum OperationOP { Op, Deop } op; @@ -36,30 +34,30 @@ const auto LambdaOP = [](CommandOrigin const& origin, CommandOutput& output, Par auto& pdb = data::PlayerNameDB::getInstance(); auto uuid = pdb.getPlayerUUID(param.name); if (!uuid.empty()) { - auto& impl = data::PlotBDStorage::getInstance(); + auto& impl = data::PlotDBStorage::getInstance(); if (param.op == ParamOp::Op) { if (impl.isAdmin(uuid)) { - sendText(output, "玩家 \"{}\" 已经是管理员!", param.name); + sendText(output, "玩家 \"{}\" 已经是管理员!", param.name); } else { if (impl.addAdmin(uuid)) { - sendText(output, "成功将玩家 \"{}\" 设为管理员!", param.name); + sendText(output, "成功将玩家 \"{}\" 设为管理员!", param.name); } else { - sendText(output, "设置玩家 \"{}\" 为管理员失败!", param.name); + sendText(output, "设置玩家 \"{}\" 为管理员失败!", param.name); } } } else if (param.op == ParamOp::Deop) { if (!impl.isAdmin(uuid)) { - sendText(output, "玩家 \"{}\" 不是管理员!", param.name); + sendText(output, "玩家 \"{}\" 不是管理员!", param.name); } else { if (impl.delAdmin(uuid)) { - sendText(output, "成功将玩家 \"{}\" 取消管理员权限!", param.name); + sendText(output, "成功将玩家 \"{}\" 取消管理员权限!", param.name); } else { - sendText(output, "取消玩家 \"{}\" 管理员权限失败!", param.name); + sendText(output, "取消玩家 \"{}\" 管理员权限失败!", param.name); } } } } else { - sendText(output, "获取玩家 \"{}\" UUID失败!", param.name); + sendText(output, "获取玩家 \"{}\" UUID失败!", param.name); } }; @@ -77,7 +75,7 @@ const auto LambdaGo = [](CommandOrigin const& origin, CommandOutput& output, Par if (param.dim == ParamGo::overworld) { player.teleport(Vec3{sw.overWorld[0], sw.overWorld[1], sw.overWorld[2]}, 0); // 传送到重生点 } else { - player.teleport(Vec3{sw.plotWorld[0], sw.plotWorld[1], sw.plotWorld[2]}, core_utils::getPlotDimensionId()); + player.teleport(Vec3{sw.plotWorld[0], sw.plotWorld[1], sw.plotWorld[2]}, core::getPlotDimensionId()); } }; #endif @@ -86,21 +84,21 @@ const auto LambdaGo = [](CommandOrigin const& origin, CommandOutput& output, Par const auto LambdaPlot = [](CommandOrigin const& origin, CommandOutput& output) { CHECK_COMMAND_TYPE(output, origin, CommandOriginType::Player); Player& player = *static_cast(origin.getEntity()); - if (player.getDimensionId() != core_utils::getPlotDimensionId()) { - sendText(player, "此命令只能在地皮世界使用!"); + if (player.getDimensionId() != core::getPlotDimensionId()) { + sendText(player, "此命令只能在地皮世界使用!"); return; } - PlotPos pos = PlotPos{player.getPosition()}; + PPos pos = PPos{player.getPosition()}; if (pos.isValid()) { - std::shared_ptr plot = data::PlotBDStorage::getInstance().getPlot(pos.getPlotID()); + std::shared_ptr plot = data::PlotDBStorage::getInstance().getPlot(pos.getPlotID()); if (plot == nullptr) { - plot = data::PlotMetadata::make(pos.getPlotID(), pos.x, pos.z); + plot = data::PlotMetadata::make(pos.getPlotID(), pos.mX, pos.mZ); } gui::PlotGUI(player, plot, false); } else { - sendText(output, "无效的地皮坐标!"); + sendText(output, "无效的地皮坐标!"); } }; @@ -114,8 +112,8 @@ const auto LambdaDefault = [](CommandOrigin const& origin, CommandOutput& output const auto LambdaDBSave = [](CommandOrigin const& origin, CommandOutput& output) { CHECK_COMMAND_TYPE(output, origin, CommandOriginType::DedicatedServer); - data::PlotBDStorage::getInstance().save(); - sendText(output, "操作完成!"); + data::PlotDBStorage::getInstance().save(); + sendText(output, "操作完成!"); }; const auto LambdaMgr = [](CommandOrigin const& origin, CommandOutput& output) { @@ -129,6 +127,120 @@ const auto LambdaSetting = [](CommandOrigin const& origin, CommandOutput& output gui::PlayerSettingGUI(player); }; +/* +namespace PlotMergeBindData { + +std::unordered_map> mBindData; // key: realName + +bool isStarted(Player& player) { return mBindData.find(player.getRealName()) != mBindData.end(); } + +const auto merge = [](CommandOrigin const& origin, CommandOutput& output) { + CHECK_COMMAND_TYPE(output, origin, CommandOriginType::Player); + Player& player = *static_cast(origin.getEntity()); + gui::PlotMergeGUI(player); +}; + +const auto start = [](CommandOrigin const& origin, CommandOutput& output) { + CHECK_COMMAND_TYPE(output, origin, CommandOriginType::Player); + Player& player = *static_cast(origin.getEntity()); + if (isStarted(player)) { + sendText(output, "请先完成当前操作!"); + return; + } + auto sou = PPos(player.getPosition()); + mBindData[string(player.getRealName())] = std::make_pair(sou, PPos()); + + sendText(player, "地皮合并已开启,前往目标地皮使用命令 /plo merge target 选择目标地皮"); + sendText(player, "已自动选择当前位置为源地皮,如需修改使用 /plo merge source"); +}; + +const auto source = [](CommandOrigin const& origin, CommandOutput& output) { + CHECK_COMMAND_TYPE(output, origin, CommandOriginType::Player); + Player& player = *static_cast(origin.getEntity()); + if (!isStarted(player)) { + sendText(output, "请先使用 /plo merge start 开启地皮合并功能!"); + return; + } + auto sou = PPos(player.getPosition()); + + mBindData[player.getRealName()].first = sou; + + sendText(player, "已更改源地皮为: {}", sou.toString()); +}; + +const auto target = [](CommandOrigin const& origin, CommandOutput& output) { + CHECK_COMMAND_TYPE(output, origin, CommandOriginType::Player); + Player& player = *static_cast(origin.getEntity()); + if (!isStarted(player)) { + sendText(output, "请先使用 /plo merge start 开启地皮合并功能!"); + return; + } + auto& dt = mBindData[player.getRealName()]; + + dt.second = PPos(player.getPosition()); + + sendText(player, "已选择目标地皮: {}", dt.second.toString()); + sendText(player, "使用 /plo merge confirm 确认合并"); +}; + +const auto confirm = [](CommandOrigin const& origin, CommandOutput& output) { + CHECK_COMMAND_TYPE(output, origin, CommandOriginType::Player); + Player& player = *static_cast(origin.getEntity()); + if (!isStarted(player)) { + sendText(output, "请先使用 /plo merge start 开启地皮合并功能!"); + return; + } + + auto& dt = mBindData[player.getRealName()]; + if (!dt.first.isValid() || !dt.second.isValid()) { + sendText(output, "源地皮或目标地皮无效,请重新选择"); + return; + } + + auto firID = dt.first.getPlotID(); + auto secID = dt.second.getPlotID(); + + if (!PPos::isAdjacent(dt.first, dt.second)) { + sendText(output, "{} 和 {} 不是相邻地皮", firID, secID); + return; + } + + auto& db = data::PlotDBStorage::getInstance(); + auto firMeta = db.getPlot(firID); + auto secMeta = db.getPlot(secID); + + if (!firMeta || !secMeta) { + sendText(output, "源地皮或目标地皮无主,请重新选择"); + return; + } + + auto uuid = player.getUuid().asString(); + if (!firMeta->isOwner(uuid) || !secMeta->isOwner(uuid)) { + sendText(output, "您不是源地皮或目标地皮的主人,请重新选择"); + return; + } + + if (db.isMergedPlot(firID)) { + firID = db.getOwnerPlotID(firID); + } + + + const bool ok = db.tryMergePlot(dt.first, dt.second); +}; + +const auto cancel = [](CommandOrigin const& origin, CommandOutput& output) { + CHECK_COMMAND_TYPE(output, origin, CommandOriginType::Player); + Player& player = *static_cast(origin.getEntity()); + if (!isStarted(player)) { + sendText(output, "您未开启地皮合并功能,无需取消"); + return; + } + mBindData.erase(player.getRealName()); + sendText(player, "操作已取消"); +}; + +}; // namespace PlotMergeBindData + */ bool registerCommand() { auto& cmd = ll::command::CommandRegistrar::getInstance().getOrCreateCommand("plo", "PlotCraft"); @@ -142,6 +254,15 @@ bool registerCommand() { cmd.overload().text("setting").execute(LambdaSetting); // plo setting cmd.overload().execute(LambdaDefault); // plo + // cmd.overload().text("merge").execute(PlotMergeBindData::merge); // plo merge + // cmd.overload().text("merge").text("start").execute(PlotMergeBindData::start); // plo merge start + // cmd.overload().text("merge").text("source").execute(PlotMergeBindData::source); // plo merge source + // cmd.overload().text("merge").text("target").execute(PlotMergeBindData::target); // plo merge target + // cmd.overload().text("merge").text("confirm").execute(PlotMergeBindData::confirm); // plo merge confirm + // cmd.overload().text("merge").text("cancel").execute(PlotMergeBindData::cancel); // plo merge cancel + + _setupTemplateCommand(); + #ifndef OVERWORLD cmd.overload().text("go").required("dim").execute(LambdaGo); // plo go #endif diff --git a/src/plotcraft/command/Command.h b/src/plotcraft/command/Command.h index dbe758d..5701d4f 100644 --- a/src/plotcraft/command/Command.h +++ b/src/plotcraft/command/Command.h @@ -20,7 +20,7 @@ #include "mc/world/level/block/actor/BlockActor.h" #include "mc/world/level/chunk/LevelChunk.h" #include "mc/world/level/dimension/Dimension.h" -#include "plotcraft/utils/Text.h" +#include "plotcraft/utils/Mc.h" #include #include #include @@ -67,6 +67,7 @@ using ll::command::CommandRegistrar; namespace plo::command { bool registerCommand(); +void _setupTemplateCommand(); // ------------------------------ tools ---------------------------------- inline string CommandOriginTypeToString(CommandOriginType type) { diff --git a/src/plotcraft/command/TemplateCommand.cc b/src/plotcraft/command/TemplateCommand.cc new file mode 100644 index 0000000..93ad6e9 --- /dev/null +++ b/src/plotcraft/command/TemplateCommand.cc @@ -0,0 +1,187 @@ +#include "Command.h" +#include "ll/api/command/CommandRegistrar.h" +#include "mc/server/commands/CommandBlockName.h" +#include "mc/server/commands/CommandOrigin.h" +#include "mc/server/commands/CommandOriginType.h" +#include "mc/server/commands/CommandOutput.h" +#include "mc/server/commands/CommandSelector.h" +#include "plotcraft/core/TemplateManager.h" +#include "plotcraft/data/PlotDBStorage.h" + +namespace plo::command { +using namespace plo::data; +using namespace plo::mc; +using TemplateManager = core::TemplateManager; + +struct StartData { + int starty; + int endy; + int roadwidth; + bool fillBedrock; + CommandBlockName defaultBlock; +}; +struct ExecuteData { + std::string fileName; +}; + + +const auto start = [](CommandOrigin const& ori, CommandOutput& out, StartData const& data) { + CHECK_COMMAND_TYPE(out, ori, CommandOriginType::Player); + auto& player = *static_cast(ori.getEntity()); + if (!PlotDBStorage::getInstance().isAdmin(player.getUuid().asString())) { + sendText(player, "此命令仅限地皮管理员使用"); + return; + } + if (core::TemplateManager::isRecordTemplateing()) { + sendText(player, "当前正在记录模板, 请不要重复执行"); + return; + } + + auto bl = data.defaultBlock.resolveBlock(data.defaultBlock.id).getBlock(); + if (bl == nullptr) { + sendText(player, "获取默认方块失败"); + return; + } + + bool ok = TemplateManager::prepareRecordTemplate( + data.starty, + data.endy, + data.roadwidth, + data.fillBedrock, + bl->getTypeName() + ); + + if (ok) { + sendText(player, "模板记录已开启,请设置起始点pos1和结束点pos2"); + } else { + sendText(player, "模板记录开启失败"); + } +}; +const auto pos1 = [](CommandOrigin const& ori, CommandOutput& out) { + CHECK_COMMAND_TYPE(out, ori, CommandOriginType::Player); + auto& player = *static_cast(ori.getEntity()); + if (!PlotDBStorage::getInstance().isAdmin(player.getUuid().asString())) { + sendText(player, "此命令仅限地皮管理员使用"); + return; + } + if (!core::TemplateManager::isRecordTemplateing()) { + sendText(player, "当前没有正在记录模板"); + return; + } + + auto cps = ChunkPos(player.getPosition()); + if (cps.x != 0 && cps.z != 0) { + sendText(player, "目前只支持在0,0坐标设置起始点"); + return; + } + + bool ok = TemplateManager::postRecordTemplateStart(cps); + if (ok) { + sendText(player, "模板记录起始点设置成功"); + } else { + sendText(player, "模板记录起始点设置失败"); + } +}; +const auto pos2 = [](CommandOrigin const& ori, CommandOutput& out) { + CHECK_COMMAND_TYPE(out, ori, CommandOriginType::Player); + auto& player = *static_cast(ori.getEntity()); + if (!PlotDBStorage::getInstance().isAdmin(player.getUuid().asString())) { + sendText(player, "此命令仅限地皮管理员使用"); + return; + } + if (!core::TemplateManager::isRecordTemplateing()) { + sendText(player, "当前没有正在记录模板"); + return; + } + + auto cps = ChunkPos(player.getPosition()); + if (cps.x != cps.z) { + sendText(player, "仅支持正方形地皮模板"); + return; + } + + bool ok = TemplateManager::postRecordTemplateEnd(cps); + if (ok) { + sendText(player, "模板记录结束点设置成功"); + } else { + sendText(player, "模板记录结束点设置失败"); + } +}; + +const auto execute = [](CommandOrigin const& ori, CommandOutput& out, ExecuteData const& data) { + CHECK_COMMAND_TYPE(out, ori, CommandOriginType::Player); + auto& player = *static_cast(ori.getEntity()); + if (!PlotDBStorage::getInstance().isAdmin(player.getUuid().asString())) { + sendText(player, "此命令仅限地皮管理员使用"); + return; + } + if (!core::TemplateManager::isRecordTemplateing()) { + sendText(player, "当前没有正在记录模板"); + return; + } + + if (data.fileName.empty()) { + sendText(player, "请输入模板文件名"); + return; + } + + sendText(player, "正在记录模板,请稍候..."); + sendText(player, "模板记录期间服务端可能未响应,请等待插件处理完成..."); + + bool ok = TemplateManager::postRecordAndSaveTemplate(data.fileName, player); + if (ok) { + sendText(player, "模板记录完成"); + } else { + sendText(player, "模板记录失败"); + } +}; + +const auto reset = [](CommandOrigin const& ori, CommandOutput& out) { + CHECK_COMMAND_TYPE(out, ori, CommandOriginType::Player); + auto& player = *static_cast(ori.getEntity()); + if (!PlotDBStorage::getInstance().isAdmin(player.getUuid().asString())) { + sendText(player, "此命令仅限地皮管理员使用"); + return; + } + if (!core::TemplateManager::isRecordTemplateing()) { + sendText(player, "当前没有正在记录模板"); + return; + } + + bool ok = TemplateManager::resetRecordTemplate(); + if (ok) { + sendText(player, "模板记录已重置"); + } else { + sendText(player, "模板记录重置失败"); + } +}; + + +void _setupTemplateCommand() { + auto& cmd = ll::command::CommandRegistrar::getInstance().getOrCreateCommand("plo"); + + // plo template record start + cmd.overload() + .text("template") + .text("record") + .text("start") + .required("starty") + .required("endy") + .required("roadwidth") + .required("fillBedrock") + .required("defaultBlock") + .execute(start); + + // plo template record pos1 + cmd.overload().text("template").text("record").text("pos1").execute(pos1); + + // plo template record pos2 + cmd.overload().text("template").text("record").text("pos2").execute(pos2); + + // plo template record execute + cmd.overload().text("template").text("record").text("execute").required("fileName").execute(execute); + + // plo template record reset + cmd.overload().text("template").text("record").text("reset").execute(reset); +} +} // namespace plo::command \ No newline at end of file diff --git a/src/plotcraft/core/CoreUtils.h b/src/plotcraft/core/CoreUtils.h deleted file mode 100644 index 9897dc0..0000000 --- a/src/plotcraft/core/CoreUtils.h +++ /dev/null @@ -1,23 +0,0 @@ -#include "mc/world/level/levelgen/WorldGenerator.h" -#include - - -#ifdef GEN_1 -#include "PlotGenerator.h" -#endif - -#ifdef GEN_2 -#include "PlotGenerator2.h" -#endif - - -namespace plo::core_utils { - - -using Generator = core::PlotGenerator; - - -int getPlotDimensionId(); - - -} // namespace plo::core_utils diff --git a/src/plotcraft/core/PlotGenerator.cc b/src/plotcraft/core/DefaultGenerator.cc similarity index 87% rename from src/plotcraft/core/PlotGenerator.cc rename to src/plotcraft/core/DefaultGenerator.cc index 5d4ec3e..f3e7d2c 100644 --- a/src/plotcraft/core/PlotGenerator.cc +++ b/src/plotcraft/core/DefaultGenerator.cc @@ -1,14 +1,5 @@ -#ifdef GEN_1 -#include "PlotGenerator.h" -#include "plotcraft/Config.h" -#include -#include -#include - -#include "fmt/format.h" -#include "mc/deps/core/data/DividedPos2d.h" +#include "DefaultGenerator.h" #include "mc/deps/core/utility/buffer_span_mut.h" -#include "mc/network/packet/UpdateSubChunkBlocksPacket.h" #include "mc/world/level/BlockPos.h" #include "mc/world/level/BlockSource.h" #include "mc/world/level/ChunkBlockPos.h" @@ -20,19 +11,18 @@ #include "mc/world/level/block/BlockVolume.h" #include "mc/world/level/block/actor/BlockActor.h" #include "mc/world/level/block/registry/BlockTypeRegistry.h" -#include "mc/world/level/block/utils/BedrockBlockNames.h" #include "mc/world/level/block/utils/VanillaBlockTypeIds.h" -#include "mc/world/level/chunk/ChunkViewSource.h" #include "mc/world/level/chunk/LevelChunk.h" -#include "mc/world/level/chunk/PostprocessingManager.h" #include "mc/world/level/levelgen/v1/ChunkLocalNoiseCache.h" +#include "plotcraft/Config.h" #include -#include +#include +#include namespace plo::core { -PlotGenerator::PlotGenerator(Dimension& dimension, uint seed, Json::Value const& generationOptionsJSON) +DefaultGenerator::DefaultGenerator(Dimension& dimension, uint seed, Json::Value const& generationOptionsJSON) : FlatWorldGenerator(dimension, seed, generationOptionsJSON) { mBiome = getLevel().getBiomeRegistry().lookupByHash(VanillaBiomeNames::Plains); mBiomeSource = std::make_unique(*mBiome); @@ -81,6 +71,10 @@ PlotGenerator::PlotGenerator(Dimension& dimension, uint seed, Json::Value const& auto end = mTemplateSubChunks[mSubChunkNum - 1].get(); end->mBlocks.mEnd = &*mVector_Dirt15_Grass1.end(); // 指向 mVector_Dirt15_Grass1 end->mBlocks.mBegin = &*mVector_Dirt15_Grass1.begin(); + + // 设置群系 + mBiomeSource = + std::make_unique(*dimension.getBiomeRegistry().lookupByHash(VanillaBiomeNames::Plains)); } @@ -95,7 +89,7 @@ int positiveMod(int value, int modulus) { } -void PlotGenerator::loadChunk(LevelChunk& levelchunk, bool /* forceImmediateReplacementDataLoad */) { +void DefaultGenerator::loadChunk(LevelChunk& levelchunk, bool /* forceImmediateReplacementDataLoad */) { auto& gen = config::cfg.generator; auto blockSource = &mDimension->getBlockSourceFromMainChunkSource(); @@ -140,18 +134,21 @@ void PlotGenerator::loadChunk(LevelChunk& levelchunk, bool /* forceImmediateRepl blockSource, nullptr ); - } else { - // 地皮内部(使用模板地形生成的草方块) - // levelchunk - // .setBlock(ChunkBlockPos{BlockPos(x, mGeneratorY, z), -64}, *mBlock_Fill, blockSource, nullptr); } + // else { + // // 地皮内部(使用模板地形生成的草方块) + // levelchunk + // .setBlock(ChunkBlockPos{BlockPos(x, mGeneratorY, z), -64}, *mBlock_Fill, blockSource, nullptr); + // } } } + // try fill biomes + this->mBiomeSource->fillBiomes(levelchunk, ChunkLocalNoiseCache{}); + levelchunk.recomputeHeightMap(0); // 重新计算高度图 levelchunk.setSaved(); levelchunk.changeState(ChunkState::Generating, ChunkState::Generated); } } // namespace plo::core -#endif // GEN_1 \ No newline at end of file diff --git a/src/plotcraft/core/PlotGenerator.h b/src/plotcraft/core/DefaultGenerator.h similarity index 80% rename from src/plotcraft/core/PlotGenerator.h rename to src/plotcraft/core/DefaultGenerator.h index 6f8f113..054bcb4 100644 --- a/src/plotcraft/core/PlotGenerator.h +++ b/src/plotcraft/core/DefaultGenerator.h @@ -1,15 +1,8 @@ -#ifdef GEN_1 #pragma once #include "mc/world/level/block/Block.h" -#include - -#include "mc/deps/core/data/DividedPos2d.h" -#include "mc/deps/core/utility/buffer_span.h" -#include "mc/util/Random.h" #include "mc/world/level/block/BlockVolume.h" #include "mc/world/level/levelgen/flat/FlatWorldGenerator.h" - - +#include #include class ChunkViewSource; @@ -20,7 +13,7 @@ namespace plo::core { using TemplateSubChunkVector = std::vector; -class PlotGenerator : public FlatWorldGenerator { +class DefaultGenerator : public FlatWorldGenerator { public: Block const* mBlock_Dirt; // 初始方块:泥土 Block const* mBlock_Bedrock; // 初始方块:基岩 @@ -39,10 +32,8 @@ class PlotGenerator : public FlatWorldGenerator { std::vector> mTemplateSubChunks; // 子区块地形模板 - PlotGenerator(Dimension& dimension, uint seed, Json::Value const& generationOptionsJSON); + DefaultGenerator(Dimension& dimension, uint seed, Json::Value const& generationOptionsJSON); void loadChunk(LevelChunk& levelchunk, bool forceImmediateReplacementDataLoad); }; } // namespace plo::core - -#endif // GEN_1 \ No newline at end of file diff --git a/src/plotcraft/core/Hook.cc b/src/plotcraft/core/Hook.cc deleted file mode 100644 index d65da03..0000000 --- a/src/plotcraft/core/Hook.cc +++ /dev/null @@ -1,35 +0,0 @@ -#ifdef OVERWORLD -#include "ll/api/memory/Hook.h" -#include "ll/api/memory/Memory.h" -#include "ll/api/service/Bedrock.h" -#include "mc/world/level/Level.h" -#include "mc/world/level/dimension/OverworldDimension.h" -#include "mc/world/level/levelgen/structure/StructureFeatureRegistry.h" -#include "mc/world/level/levelgen/structure/StructureSetRegistry.h" -#include "mc/world/level/levelgen/structure/VillageFeature.h" - -#include "CoreUtils.h" - -LL_AUTO_TYPE_INSTANCE_HOOK( - OverworldDimensionCreateGeneratorHook, - ll::memory::HookPriority::Normal, - OverworldDimension, - "?createGenerator@OverworldDimension@@UEAA?AV?$unique_ptr@VWorldGenerator@@U?$default_delete@VWorldGenerator@@@std@" - "@@std@@AEBVStructureSetRegistry@worldgen@br@@@Z", - std::unique_ptr, - br::worldgen::StructureSetRegistry const& -) { - mSeaLevel = -61; - - std::unique_ptr worldGenerator; - auto seed = getLevel().getSeed(); - auto& levelData = getLevel().getLevelData(); - - worldGenerator = - std::make_unique(*this, seed, levelData.getFlatWorldGeneratorOptions()); - - worldGenerator->init(); - return std::move(worldGenerator); -} - -#endif // OVERWORLD \ No newline at end of file diff --git a/src/plotcraft/core/OverworldHook.cc b/src/plotcraft/core/OverworldHook.cc new file mode 100644 index 0000000..0e6d2ae --- /dev/null +++ b/src/plotcraft/core/OverworldHook.cc @@ -0,0 +1,40 @@ +#ifdef OVERWORLD +#include "DefaultGenerator.h" +#include "TemplateGenerator.h" +#include "ll/api/memory/Hook.h" +#include "ll/api/service/Bedrock.h" +#include "mc/world/level/Level.h" +#include "mc/world/level/dimension/OverworldDimension.h" +#include "mc/world/level/levelgen/WorldGenerator.h" +#include "mc/world/level/levelgen/structure/StructureSetRegistry.h" +#include "plotcraft/Config.h" + + +LL_AUTO_TYPE_INSTANCE_HOOK( + OverworldDimensionCreateGeneratorHook, + ll::memory::HookPriority::Normal, + OverworldDimension, + // "?createGenerator@OverworldDimension@@UEAA?AV?$unique_ptr@VWorldGenerator@@U?$default_delete@VWorldGenerator@@@std@" + // "@@std@@AEBVStructureSetRegistry@worldgen@br@@@Z", + &OverworldDimension::createGenerator, + std::unique_ptr, + br::worldgen::StructureSetRegistry const& +) { + std::unique_ptr worldGenerator; + mSeaLevel = -61; + auto seed = getLevel().getSeed(); + auto& levelData = getLevel().getLevelData(); + + if (plo::config::cfg.generator.type == plo::config::PlotGeneratorType::Default) { + worldGenerator = + std::make_unique(*this, seed, levelData.getFlatWorldGeneratorOptions()); + } else { + worldGenerator = + std::make_unique(*this, seed, levelData.getFlatWorldGeneratorOptions()); + } + + worldGenerator->init(); + return std::move(worldGenerator); +} + +#endif // OVERWORLD \ No newline at end of file diff --git a/src/plotcraft/core/PPos.cc b/src/plotcraft/core/PPos.cc new file mode 100644 index 0000000..8630e46 --- /dev/null +++ b/src/plotcraft/core/PPos.cc @@ -0,0 +1,437 @@ +#include "plotcraft/core/PPos.h" +#include "fmt/format.h" +#include "plotcraft/Config.h" +#include "plotcraft/core/TemplateManager.h" +#include +#include +#include + + +namespace plo { +using TemplateManager = core::TemplateManager; + +// !Class: PPos +PPos::PPos() : mX(0), mZ(0) {} +PPos::PPos(int x, int z) : mX(x), mZ(z) { + auto& cfg = config::cfg.generator; + + Vec3 min, max; + + // 计算地皮的四个顶点 + if (!TemplateManager::isUseTemplate()) { + // DefaultGenerator + int total = cfg.plotWidth + cfg.roadWidth; + min = Vec3{x * total, -64, z * total}; + max = Vec3{min.x + cfg.plotWidth - 1, 320, min.z + cfg.plotWidth - 1}; + + } else { + // TemplateGenerator + int r = TemplateManager::getCurrentTemplateRoadWidth(); + int total = TemplateManager::getCurrentTemplateChunkNum() * 16; + min = Vec3{x * total + r, -64, z * total + r}; + max = Vec3{min.x + total - r, 320, min.z + total - r}; + } + + // 按顺时针顺序存储顶点 + mVertexs = { + min, // 左下角 + Vec3{max.x, min.y, min.z}, // 右下角 + max, // 右上角 + Vec3{min.x, min.y, max.z}, // 左上角 + min // 回到起点,形成闭合多边形 + }; +} + +PPos::PPos(const Vec3& vec3) { + auto& cfg = config::cfg.generator; + + // 计算总长度 + bool const isUseTemplate = TemplateManager::isUseTemplate(); + int const roadWidth = isUseTemplate ? TemplateManager::getCurrentTemplateRoadWidth() : cfg.roadWidth; + int total = isUseTemplate ? (TemplateManager::getCurrentTemplateChunkNum() * 16) : (cfg.plotWidth + cfg.roadWidth); + int width = isUseTemplate ? (total - (roadWidth * 2)) : cfg.plotWidth; + int localX = static_cast(std::floor(vec3.x)) % total; + int localZ = static_cast(std::floor(vec3.z)) % total; + + // 计算地皮坐标 + mX = std::floor(vec3.x / total); + mZ = std::floor(vec3.z / total); + + Vec3 min, max; + bool isValid = true; + + if (!TemplateManager::isUseTemplate()) { + // DefaultGenerator + if (localX < 0) localX += total; + if (localZ < 0) localZ += total; + if (localX >= width || localZ >= width) { + isValid = false; + } else { + min = Vec3{mX * total, -64, mZ * total}; + max = Vec3{min.x + width - 1, 320, min.z + width - 1}; + } + + } else { + // TemplateGenerator + if (localX < 1) localX += total; + if (localZ < 1) localZ += total; + if (localX > width || localZ > width) { + isValid = false; + } else { + min = Vec3{mX * total + roadWidth, -64, mZ * total + roadWidth}; + max = Vec3{min.x + width - roadWidth, 320, min.z + width - roadWidth}; + } + } + + + if (isValid) { + // 按顺时针顺序存储顶点 + mVertexs = { + min, // 左下角 + Vec3{max.x, min.y, min.z}, // 右下角 + max, // 右上角 + Vec3{min.x, min.y, max.z}, // 左上角 + min // 回到起点,形成闭合多边形 + }; + } else { + mVertexs.clear(); + mX = 0; + mZ = 0; + } +} +int PPos::getSurfaceY() const { + return TemplateManager::isUseTemplate() ? (TemplateManager::mTemplateData.template_offset + 1) + : -64 + (config::cfg.generator.subChunkNum * 16); +} +bool PPos::isValid() const { return !mVertexs.empty(); } +string PPos::getPlotID() const { return fmt::format("({0},{1})", mX, mZ); } +Vec3 PPos::getSafestPos() const { + if (isValid()) { + auto& v3 = mVertexs[0]; + return Vec3{v3.x, getSurfaceY() + 1, v3.z}; + } + return Vec3{}; +} + + +bool PPos::isPosInPlot(const Vec3& vec3) const { + if (vec3.y < -64 || vec3.y > 320) { + return false; + } + return isPointInPolygon(vec3, mVertexs); +} + +bool PPos::isPosOnBorder(const Vec3& vec3) const { + if (vec3.y < -64 || vec3.y > 320) { + return false; + } + + // 检查点是否在任何边上 + for (size_t i = 0; i < mVertexs.size() - 1; ++i) { + const Vec3& v1 = mVertexs[i]; + const Vec3& v2 = mVertexs[i + 1]; + + // 检查点是否在当前边上 + if ((vec3.x >= std::min(v1.x, v2.x) && vec3.x <= std::max(v1.x, v2.x)) + && (vec3.z >= std::min(v1.z, v2.z) && vec3.z <= std::max(v1.z, v2.z))) { + // 如果边是垂直的 + if (v1.x == v2.x) { + if (vec3.x == v1.x) return true; + } + // 如果边是水平的 + else if (v1.z == v2.z) { + if (vec3.z == v1.z) return true; + } + // 如果边是斜的(虽然在这个情况下不太可能) + // else { + // double slope = (v2.z - v1.z) / (v2.x - v1.x); + // double intercept = v1.z - slope * v1.x; + // if (std::abs(vec3.z - (slope * vec3.x + intercept)) < 1e-6) return true; + // } + } + } + + return false; +} +bool PPos::isCubeOnBorder(Cube const& cube) const { + if (!isValid()) { + return false; + } + + // 检查Cube是否完全在地皮外部或内部 + bool allInside = true; + bool allOutside = true; + for (const auto& corner : cube.get2DVertexs()) { + bool inside = isPosInPlot(corner); + allInside &= inside; + allOutside &= !inside; + if (!allInside && !allOutside) { + break; + } + } + if (allInside || allOutside) { + return false; + } + + // 检查Cube的边是否与地皮边界相交 + for (size_t i = 0; i < mVertexs.size() - 1; ++i) { + const BlockPos& v1 = mVertexs[i]; + const BlockPos& v2 = mVertexs[i + 1]; + + // 检查水平边 + if (v1.z == v2.z) { + if (cube.mMin.z <= v1.z && cube.mMax.z >= v1.z + && std::max(cube.mMin.x, std::min(v1.x, v2.x)) <= std::min(cube.mMax.x, std::max(v1.x, v2.x))) { + return true; + } + } + // 检查垂直边 + else if (v1.x == v2.x) { + if (cube.mMin.x <= v1.x && cube.mMax.x >= v1.x + && std::max(cube.mMin.z, std::min(v1.z, v2.z)) <= std::min(cube.mMax.z, std::max(v1.z, v2.z))) { + return true; + } + } + } + + return false; +} +bool PPos::isRadiusOnBorder(class Radius const& radius) const { + if (!isValid()) { + return false; + } + + // 快速检查:如果圆心到地皮中心的距离大于半径加上地皮对角线的一半,则一定不相交 + Vec3 plotCenter = (mVertexs[0] + mVertexs[2]) * 0.5; + double dx = radius.mCenter.x - plotCenter.x; + double dz = radius.mCenter.z - plotCenter.z; + double centerDist = std::sqrt(dx * dx + dz * dz); + double plotRadius = (mVertexs[2] - mVertexs[0]).length() * 0.5; + if (centerDist > radius.mRadius + plotRadius) { + return false; + } + + // 检查圆是否完全包含地皮或完全在地皮外部 + bool allInside = true; + bool allOutside = true; + for (const auto& vertex : mVertexs) { + dx = vertex.x - radius.mCenter.x; + dz = vertex.z - radius.mCenter.z; + double distSquared = dx * dx + dz * dz; + if (distSquared <= radius.mRadius * radius.mRadius) { + allOutside = false; + } else { + allInside = false; + } + if (!allInside && !allOutside) { + break; + } + } + if (allInside || allOutside) { + return false; + } + + // 检查圆是否与地皮的边相交 + for (size_t i = 0; i < mVertexs.size() - 1; ++i) { + const Vec3& v1 = mVertexs[i]; + const Vec3& v2 = mVertexs[i + 1]; + + // 计算边的方向向量 + double edgeX = v2.x - v1.x; + double edgeZ = v2.z - v1.z; + + // 计算从圆心到边起点的向量 + double vecX = radius.mCenter.x - v1.x; + double vecZ = radius.mCenter.z - v1.z; + + // 计算边的长度的平方 + double edgeLengthSquared = edgeX * edgeX + edgeZ * edgeZ; + + // 计算圆心到边的投影长度比例 + double t = (vecX * edgeX + vecZ * edgeZ) / edgeLengthSquared; + t = std::max(0.0, std::min(1.0, t)); + + // 计算圆心到边的最近点 + double nearestX = v1.x + t * edgeX; + double nearestZ = v1.z + t * edgeZ; + + // 计算圆心到最近点的距离 + double distX = radius.mCenter.x - nearestX; + double distZ = radius.mCenter.z - nearestZ; + double distSquared = distX * distX + distZ * distZ; + + // 如果距离小于等于半径,则相交 + if (distSquared <= radius.mRadius * radius.mRadius) { + return true; + } + } + + return false; +} + + +string PPos::toString() const { +#if !defined(DEBUG) + return fmt::format("{0} | Vertex: {1}", getPlotID(), mVertexs.size()); +#else + string dbg; + size_t i = 0; + for (auto& v : mVertexs) { + dbg += fmt::format("[{0}] => {1}\n", i++, v.toString()); + } + return fmt::format("{0} | Vertex: {1}\n{2}", getPlotID(), mVertexs.size(), dbg); +#endif +} + + +bool PPos::operator!=(PPos const& other) const { return !(*this == other); } +bool PPos::operator==(PPos const& other) const { return other.mVertexs == this->mVertexs; } + + +// static +bool PPos::isAdjacent(const PPos& plot1, const PPos& plot2) { + int dx = std::abs(plot1.mX - plot2.mX); + int dz = std::abs(plot1.mZ - plot2.mZ); + + // 两个地皮相邻的条件: + // 1. x坐标相同,z坐标相差1,或者 + // 2. z坐标相同,x坐标相差1 + // 3. 两个地皮都是有效的 + return ((dx == 0 && dz == 1) || (dx == 1 && dz == 0)) && (plot1.isValid() && plot2.isValid()); +} +// 判断点是否在多边形内部(射线法) +bool PPos::isPointInPolygon(const Vec3& point, Vertexs const& polygon) { + bool inside = false; + int n = polygon.size(); + for (int i = 0, j = n - 1; i < n; j = i++) { + if (((polygon[i].z <= point.z && point.z < polygon[j].z) || (polygon[j].z <= point.z && point.z < polygon[i].z)) + && (point.x < (polygon[j].x - polygon[i].x) * (point.z - polygon[i].z) / (polygon[j].z - polygon[i].z) + + polygon[i].x)) { + inside = !inside; + } + } + return inside; +} + + +// !Class: Cube +Cube::Cube(BlockPos const& min, BlockPos const& max) : mMin(min), mMax(max) { + if (mMin.x > mMax.x) std::swap(mMin.x, mMax.x); + if (mMin.y > mMax.y) std::swap(mMin.y, mMax.y); + if (mMin.z > mMax.z) std::swap(mMin.z, mMax.z); +} + +Vertexs Cube::get2DVertexs() const { + return { + Vec3{mMin.x, 0, mMin.z}, // 左下 + Vec3{mMax.x, 0, mMin.z}, // 右下 + Vec3{mMax.x, 0, mMax.z}, // 右上 + Vec3{mMin.x, 0, mMax.z}, // 左上 + Vec3{mMin.x, 0, mMin.z} // 回到起点,形成闭合多边形 + }; +} + +bool Cube::hasPos(BlockPos const& pos) const { + return pos.x >= mMin.x && pos.x <= mMax.x && pos.y >= mMin.y && pos.y <= mMax.y && pos.z >= mMin.z + && pos.z <= mMax.z; +} + +std::vector Cube::getRangedPlots() const { + std::vector rangedPlots; + + // 获取配置信息 + auto& cfg = config::cfg.generator; + bool isUseTemplate = TemplateManager::isUseTemplate(); + int total = isUseTemplate ? (TemplateManager::getCurrentTemplateChunkNum() * 16) : (cfg.plotWidth + cfg.roadWidth); + + // 计算可能涉及的地皮范围 + int minPlotX = std::floor(static_cast(mMin.x) / total); + int maxPlotX = std::ceil(static_cast(mMax.x) / total); + int minPlotZ = std::floor(static_cast(mMin.z) / total); + int maxPlotZ = std::ceil(static_cast(mMax.z) / total); + + // 遍历可能的地皮 + for (int x = minPlotX; x <= maxPlotX; ++x) { + for (int z = minPlotZ; z <= maxPlotZ; ++z) { + PPos plot(x, z); + + // 检查地皮是否与Cube有交集 + if (plot.isValid()) { + bool hasIntersection = false; + for (const auto& vertex : plot.mVertexs) { + if (vertex.x >= mMin.x && vertex.x <= mMax.x && vertex.z >= mMin.z && vertex.z <= mMax.z + && mMin.y <= 320 && mMax.y >= -64) { + hasIntersection = true; + break; + } + } + + if (hasIntersection) { + rangedPlots.push_back(plot); + } + } + } + } + + return rangedPlots; +} +bool Cube::operator==(const Cube& other) const { return mMin == other.mMin && mMax == other.mMax; }; +bool Cube::operator!=(const Cube& other) const { return !(*this == other); }; + +// static +bool Cube::isCollision(Cube const& cube1, Cube const& cube2) { + return !( + cube1.mMax.x < cube2.mMin.x || cube1.mMin.x > cube2.mMax.x || cube1.mMax.y < cube2.mMin.y + || cube1.mMin.y > cube2.mMax.y || cube1.mMax.z < cube2.mMin.z || cube1.mMin.z > cube2.mMax.z + ); +} + + +// !Class: Radius +std::vector Radius::getRangedPlots() const { + std::vector rangedPlots; + + // 获取配置信息 + auto& cfg = config::cfg.generator; + bool isUseTemplate = TemplateManager::isUseTemplate(); + int total = isUseTemplate ? (TemplateManager::getCurrentTemplateChunkNum() * 16) : (cfg.plotWidth + cfg.roadWidth); + + // 计算可能涉及的地皮范围 + int minPlotX = std::floor((mCenter.x - mRadius) / static_cast(total)); + int maxPlotX = std::ceil((mCenter.x + mRadius) / static_cast(total)); + int minPlotZ = std::floor((mCenter.z - mRadius) / static_cast(total)); + int maxPlotZ = std::ceil((mCenter.z + mRadius) / static_cast(total)); + + // 遍历可能的地皮 + for (int x = minPlotX; x <= maxPlotX; ++x) { + for (int z = minPlotZ; z <= maxPlotZ; ++z) { + PPos plot(x, z); + + // 检查地皮是否与半径相交 + if (plot.isValid()) { + bool hasIntersection = false; + for (const auto& vertex : plot.mVertexs) { + double dx = vertex.x - mCenter.x; + double dz = vertex.z - mCenter.z; + if (dx * dx + dz * dz <= mRadius * mRadius) { + hasIntersection = true; + break; + } + } + + if (hasIntersection) { + rangedPlots.push_back(plot); + } + } + } + } + + return rangedPlots; +} +bool Radius::operator==(const Radius& other) const { + return this->mCenter == other.mCenter && this->mRadius == other.mRadius; +} +bool Radius::operator!=(const Radius& other) const { return !(*this == other); } + + +} // namespace plo \ No newline at end of file diff --git a/src/plotcraft/core/PlotDimension.cc b/src/plotcraft/core/PlotDimension.cc index c25d850..82ecb63 100644 --- a/src/plotcraft/core/PlotDimension.cc +++ b/src/plotcraft/core/PlotDimension.cc @@ -1,7 +1,7 @@ #ifndef OVERWORLD - #include "PlotDimension.h" - +#include "DefaultGenerator.h" +#include "TemplateGenerator.h" #include "mc/world/level/BlockSource.h" #include "mc/world/level/DimensionConversionData.h" #include "mc/world/level/Level.h" @@ -16,9 +16,10 @@ #include "mc/world/level/levelgen/structure/StructureSetRegistry.h" #include "mc/world/level/levelgen/structure/VillageFeature.h" #include "more_dimensions/api/dimension/CustomDimensionManager.h" +#include "plotcraft/Config.h" +#include "plotcraft/core/Utils.h" -#include "CoreUtils.h" namespace plo::core { @@ -36,12 +37,18 @@ CompoundTag PlotDimension::generateNewData() { return {}; } std::unique_ptr PlotDimension::createGenerator(br::worldgen::StructureSetRegistry const& /* structureSetRegistry */) { - auto seed = getLevel().getSeed(); - auto& levelData = getLevel().getLevelData(); + std::unique_ptr worldGenerator; + auto seed = getLevel().getSeed(); + auto& levelData = getLevel().getLevelData(); // 实例化 地皮生成器 - auto worldGenerator = - std::make_unique(*this, seed, levelData.getFlatWorldGeneratorOptions()); + if (plo::config::cfg.generator.type == plo::config::PlotGeneratorType::Default) { + worldGenerator = + std::make_unique(*this, seed, levelData.getFlatWorldGeneratorOptions()); + } else { + worldGenerator = + std::make_unique(*this, seed, levelData.getFlatWorldGeneratorOptions()); + } worldGenerator->init(); // 必须调用,初始化生成器 diff --git a/src/plotcraft/core/PlotGenerator2.cc b/src/plotcraft/core/PlotGenerator2.cc deleted file mode 100644 index 5b5b67b..0000000 --- a/src/plotcraft/core/PlotGenerator2.cc +++ /dev/null @@ -1,142 +0,0 @@ -#ifdef GEN_2 -#include "PlotGenerator2.h" -#include "plotcraft/Config.h" -#include - -#include "mc/world/level/block/Block.h" -#include "mc/world/level/block/registry/BlockTypeRegistry.h" -#include "mc/world/level/block/utils/BedrockBlockNames.h" -#include "mc/world/level/block/utils/VanillaBlockTypeIds.h" -#include "mc/world/level/chunk/LevelChunk.h" -#include "mc/world/level/levelgen/v1/ChunkLocalNoiseCache.h" - -namespace plo::core { - -PlotGenerator::PlotGenerator(Dimension& dimension, uint seed, Json::Value const& generationOptionsJSON) -: FlatWorldGenerator(dimension, seed, generationOptionsJSON) { - auto& gen = config::cfg.generator; - chunk_n = gen.plotChunkSize; - - mBlock_GrassPath = &BlockTypeRegistry::getDefaultBlockState(gen.roadBlock.c_str()); - mBlock_StoneBlockSlab = &BlockTypeRegistry::getDefaultBlockState(gen.borderBlock.c_str()); - - auto height = mPrototype.mHeight; // 16 - for (int i = 0; i < 16; i++) { - // 一般开始的是距离第一个方块的差值,然后是会乘 - // i的间隔方块,然后是与另外一条路或者半砖方块的间隔,最后面的是第几层 南边 - e_s_angle[height * 15 + height * 16 * i + 3] = mBlock_GrassPath; - s_w_angle[height * 15 + height * 16 * i + 3] = mBlock_GrassPath; - south_side[height * 15 + height * 16 * i + 3] = mBlock_GrassPath; - south_side[height * 14 + height * 16 * i + 4] = mBlock_StoneBlockSlab; - // 东边 - e_s_angle[height * 16 * 15 + height * i + 3] = mBlock_GrassPath; - n_e_angle[height * 16 * 15 + height * i + 3] = mBlock_GrassPath; - east_side[height * 16 * 15 + height * i + 3] = mBlock_GrassPath; - east_side[height * 16 * 14 + height * i + 4] = mBlock_StoneBlockSlab; - // 西边 - s_w_angle[height * i + 3] = mBlock_GrassPath; - w_n_angle[height * i + 3] = mBlock_GrassPath; - west_side[height * i + 3] = mBlock_GrassPath; - west_side[height * 16 + height * i + 4] = mBlock_StoneBlockSlab; - // 北边 - n_e_angle[height * 16 * i + 3] = mBlock_GrassPath; - w_n_angle[height * 16 * i + 3] = mBlock_GrassPath; - north_side[height * 16 * i + 3] = mBlock_GrassPath; - north_side[height + height * 16 * i + 4] = mBlock_StoneBlockSlab; - - // 半砖避免放到路上,最后一次循环 - if (i < 15) { - // 南 - e_s_angle[height * 14 + height * 16 * i + 4] = mBlock_StoneBlockSlab; - // 东 - e_s_angle[height * 16 * 14 + height * i + 4] = mBlock_StoneBlockSlab; - // 西 - s_w_angle[height * 16 + height * i + 4] = mBlock_StoneBlockSlab; - // 北 - n_e_angle[height + height * 16 * i + 4] = mBlock_StoneBlockSlab; - } - // 第一次循环 - if (i > 0) { - // 西 - w_n_angle[height * 16 + height * i + 4] = mBlock_StoneBlockSlab; - // 北 - w_n_angle[height + height * 16 * i + 4] = mBlock_StoneBlockSlab; - // 南 - s_w_angle[height * 14 + height * 16 * i + 4] = mBlock_StoneBlockSlab; - // 东 - n_e_angle[height * 16 * 14 + height * i + 4] = mBlock_StoneBlockSlab; - } - } - // 重新赋值处理 - mVol_East_Side.mBlocks.mBegin = &*east_side.begin(); - mVol_East_Side.mBlocks.mEnd = &*east_side.end(); - - mVol_South_Side.mBlocks.mBegin = &*south_side.begin(); - mVol_South_Side.mBlocks.mEnd = &*south_side.end(); - - mVol_West_Side.mBlocks.mBegin = &*west_side.begin(); - mVol_West_Side.mBlocks.mEnd = &*west_side.end(); - - mVol_North_Side.mBlocks.mBegin = &*north_side.begin(); - mVol_North_Side.mBlocks.mEnd = &*north_side.end(); - - mVol_ES_Angle.mBlocks.mBegin = &*e_s_angle.begin(); - mVol_ES_Angle.mBlocks.mEnd = &*e_s_angle.end(); - - mVol_SW_Angle.mBlocks.mBegin = &*s_w_angle.begin(); - mVol_SW_Angle.mBlocks.mEnd = &*s_w_angle.end(); - - mVol_NE_Angle.mBlocks.mBegin = &*n_e_angle.begin(); - mVol_NE_Angle.mBlocks.mEnd = &*n_e_angle.end(); - - mVol_WN_Angle.mBlocks.mBegin = &*w_n_angle.begin(); - mVol_WN_Angle.mBlocks.mEnd = &*w_n_angle.end(); -} - - -void PlotGenerator::loadChunk(LevelChunk& levelchunk, bool) { - auto chunkPos = levelchunk.getPosition(); - - int n = chunk_n; - auto pos_x = (chunkPos.x % n + n) % n; - auto pos_z = (chunkPos.z % n + n) % n; - - // 控制生成道路 - if (pos_x == 0) { - if (pos_z == 0) { - levelchunk.setBlockVolume(mVol_WN_Angle, 0); // 西北角 - } else if (pos_z == (n - 1)) { - levelchunk.setBlockVolume(mVol_SW_Angle, 0); // 西南角 - } else { - levelchunk.setBlockVolume(mVol_West_Side, 0); // 西 - } - - } else if (pos_x == (n - 1)) { - if (pos_z == 0) { - levelchunk.setBlockVolume(mVol_NE_Angle, 0); // 东北角 - } else if (pos_z == (n - 1)) { - levelchunk.setBlockVolume(mVol_ES_Angle, 0); // 东南角 - } else { - levelchunk.setBlockVolume(mVol_East_Side, 0); // 东 - } - } else if (pos_z == 0) { - levelchunk.setBlockVolume(mVol_North_Side, 0); // 北 - } else if (pos_z == (n - 1)) { - levelchunk.setBlockVolume(mVol_South_Side, 0); // 南 - } else { - levelchunk.setBlockVolume(mPrototype, 0); // 中间 - } - - - levelchunk.recomputeHeightMap(0); - // mBiomeSource = std::make_unique(*mBiome); - // ChunkLocalNoiseCache chunkLocalNoiseCache; - // mBiomeSource->fillBiomes(levelchunk, chunkLocalNoiseCache); - levelchunk.setSaved(); - levelchunk.changeState(ChunkState::Generating, ChunkState::Generated); -} - - -} // namespace plo::core - -#endif // GEN_2 \ No newline at end of file diff --git a/src/plotcraft/core/PlotGenerator2.h b/src/plotcraft/core/PlotGenerator2.h deleted file mode 100644 index a5bd19f..0000000 --- a/src/plotcraft/core/PlotGenerator2.h +++ /dev/null @@ -1,41 +0,0 @@ -#ifdef GEN_2 -#pragma once -#include "mc/world/level/block/BlockVolume.h" -#include "mc/world/level/levelgen/flat/FlatWorldGenerator.h" -#include - -namespace plo::core { - -class PlotGenerator : public FlatWorldGenerator { -public: - int chunk_n = 4; // n * n的区块 - std::vector east_side = mPrototypeBlocks; // 东侧的方块 - std::vector south_side = mPrototypeBlocks; // 南侧的方块 - std::vector west_side = mPrototypeBlocks; // 西侧的方块 - std::vector north_side = mPrototypeBlocks; // 北侧的方块 - std::vector n_e_angle = mPrototypeBlocks; // 东北角的方块 - std::vector e_s_angle = mPrototypeBlocks; // 东南角的方块 - std::vector s_w_angle = mPrototypeBlocks; // 西南角的方块 - std::vector w_n_angle = mPrototypeBlocks; // 西北角的方块 - - - Block const* mBlock_GrassPath; // 草径 - Block const* mBlock_StoneBlockSlab; // 石砖台阶 - - - BlockVolume mVol_East_Side = mPrototype; // 东 - BlockVolume mVol_South_Side = mPrototype; // 南 - BlockVolume mVol_West_Side = mPrototype; // 西 - BlockVolume mVol_North_Side = mPrototype; // 北 - BlockVolume mVol_NE_Angle = mPrototype; // 东北 - BlockVolume mVol_ES_Angle = mPrototype; // 东南 - BlockVolume mVol_SW_Angle = mPrototype; // 西南 - BlockVolume mVol_WN_Angle = mPrototype; // 西北 - - PlotGenerator(Dimension& dimension, uint seed, Json::Value const& generationOptionsJSON); - - void loadChunk(class LevelChunk& levelchunk, bool forceImmediateReplacementDataLoad); -}; - -} // namespace plo::core -#endif // GEN_2 \ No newline at end of file diff --git a/src/plotcraft/core/TemplateGenerator.cc b/src/plotcraft/core/TemplateGenerator.cc new file mode 100644 index 0000000..ee6a8ae --- /dev/null +++ b/src/plotcraft/core/TemplateGenerator.cc @@ -0,0 +1,39 @@ +#include "TemplateGenerator.h" +#include "TemplateManager.h" +#include "mc/world/level/Level.h" +#include "mc/world/level/biome/VanillaBiomeNames.h" +#include "mc/world/level/biome/registry/BiomeRegistry.h" +#include "mc/world/level/chunk/LevelChunk.h" +#include "mc/world/level/levelgen/v1/ChunkLocalNoiseCache.h" +#include + + +namespace plo::core { + + +TemplateGenerator::TemplateGenerator(Dimension& dimension, uint seed, Json::Value const& generationOptionsJSON) +: FlatWorldGenerator(dimension, seed, generationOptionsJSON) { + mBiome = getLevel().getBiomeRegistry().lookupByHash(VanillaBiomeNames::Plains); + mBiomeSource = std::make_unique(*mBiome); + + bool ok = TemplateManager::generatorBlockVolume(mPrototype); + if (!ok) { + throw std::runtime_error("Failed to generatorBlockVolume"); + } +} + + +void TemplateGenerator::loadChunk(LevelChunk& levelchunk, bool) { + auto& chunkPos = levelchunk.getPosition(); + + levelchunk.setBlockVolume(TemplateManager::mBlockVolume[TemplateManager::calculateChunkID(chunkPos)], 0); + + + levelchunk.recomputeHeightMap(0); + mBiomeSource->fillBiomes(levelchunk, ChunkLocalNoiseCache{}); + levelchunk.setSaved(); + levelchunk.changeState(ChunkState::Generating, ChunkState::Generated); +} + + +} // namespace plo::core diff --git a/src/plotcraft/core/TemplateGenerator.h b/src/plotcraft/core/TemplateGenerator.h new file mode 100644 index 0000000..bcfd7af --- /dev/null +++ b/src/plotcraft/core/TemplateGenerator.h @@ -0,0 +1,16 @@ +#pragma once +#include "mc/world/level/levelgen/flat/FlatWorldGenerator.h" + + +namespace plo::core { + + +class TemplateGenerator : public FlatWorldGenerator { +public: + TemplateGenerator(Dimension& dimension, uint seed, Json::Value const& generationOptionsJSON); + + void loadChunk(class LevelChunk& levelchunk, bool forceImmediateReplacementDataLoad); +}; + + +} // namespace plo::core diff --git a/src/plotcraft/core/TemplateManager.cc b/src/plotcraft/core/TemplateManager.cc new file mode 100644 index 0000000..3fc4298 --- /dev/null +++ b/src/plotcraft/core/TemplateManager.cc @@ -0,0 +1,289 @@ +#include "TemplateManager.h" +#include "mc/deps/core/utility/buffer_span_mut.h" +#include "mc/enums/LogLevel.h" +#include "mc/world/level/BlockSource.h" +#include "mc/world/level/ChunkPos.h" +#include "mc/world/level/block/Block.h" +#include "mc/world/level/block/BlockVolume.h" +#include "mc/world/level/block/registry/BlockTypeRegistry.h" +#include "mc/world/level/chunk/ChunkSource.h" +#include "nlohmann/json.hpp" +#include "plotcraft/Config.h" +#include "plotcraft/utils/JsonHelper.h" +#include "plotcraft/utils/Mc.h" +#include "plugin/MyPlugin.h" +#include +#include +#include + + +namespace plo::core { +namespace fs = std::filesystem; +using json = nlohmann::json; +using namespace plo::mc; + +// static member definitions +TemplateData TemplateManager::mTemplateData; +std::unordered_map TemplateManager::mBlockMap; +std::unordered_map> TemplateManager::mBlockBuffer; +std::unordered_map TemplateManager::mBlockVolume; + +TemplateData TemplateManager::mRecordData; // Record +bool TemplateManager::mCanRecord; +bool TemplateManager::mIsRecording; +ChunkPos TemplateManager::mRecordStart; +ChunkPos TemplateManager::mRecordEnd; +std::unordered_map TemplateManager::mRecordBlockIDMap; // key: typeName + + +// functions +bool TemplateManager::_parseTemplate() { + auto& data = mTemplateData; + + // 解析 block_map + auto defBlock = &BlockTypeRegistry::getDefaultBlockState(data.default_block.c_str()); + auto bedrockBlock = &BlockTypeRegistry::getDefaultBlockState("minecraft:bedrock"); + auto airBlock = &BlockTypeRegistry::getDefaultBlockState("minecraft:air"); + mBlockMap.emplace(defBlock->getTypeName(), defBlock); + mBlockMap.emplace(bedrockBlock->getTypeName(), bedrockBlock); + mBlockMap.emplace(airBlock->getTypeName(), airBlock); + for (auto& i : data.block_map) { + auto bl = &BlockTypeRegistry::getDefaultBlockState(i.second.c_str()); + mBlockMap.emplace(bl->getTypeName(), bl); + } + + + // 解析 template_data + int templateStartHeight = data.template_offset; // 模板起始高度(此高度以下的为defBlock) + if (templateStartHeight < 0) templateStartHeight += 64; // 起始高度 < 0,则+64,指向正确的buffer位置 + + int templateTotalHeight = templateStartHeight + data.template_height; // 起始高度 + 模板数据高度 + + // 计算最小的 buffer 高度 + int remainder = templateTotalHeight % 16; // 取余数 + int fixedHeight = + remainder != 0 ? (templateTotalHeight + 16 - remainder) : templateTotalHeight; // 最小的BlockVolume.y为16 + + int totalVolume = fixedHeight * 16 * 16; // 16 * 16 * fixedHeight + + // 遍历区块数据 + for (auto& [key, templateBlocks] : data.template_data) { + auto& buffer = mBlockBuffer[string(key)]; + buffer.resize(totalVolume, airBlock); // 调整大小并填充默认方块 + + // 遍历区块的 256 个Y轴 + for (int y = 0; y < 256; y++) { + if (data.fill_bedrock) buffer[y * fixedHeight] = bedrockBlock; // 每个y轴的起点设置为基岩 + + // 读 template_height 个方块数据 + int mapIndex = y * data.template_height; // block_map 的索引 + int bufferIndex = (y * fixedHeight) + templateStartHeight; // buffer 的索引 + int defIndex = (y * fixedHeight) + 1; // 默认方块下标(+1避开基岩) + for (int r = 0; r < fixedHeight; r++) { + if (r < data.template_height) { + Block const* bl = mBlockMap[data.block_map[std::to_string(templateBlocks[mapIndex + r])]]; + buffer[bufferIndex + r] = bl; + } + if (r < templateStartHeight - 1) { + buffer[defIndex + r] = defBlock; + } + } + } + } + + return true; +} +bool TemplateManager::loadTemplate(const string& path) { + if (!fs::exists(path)) return false; + + std::ifstream file(path); + if (!file.is_open()) return false; + + auto jdata = json::parse(file); + utils::JsonHelper::jsonToStructNoMerge(jdata, mTemplateData); + + return _parseTemplate(); +} +bool TemplateManager::generatorBlockVolume(BlockVolume& volume) { + auto& data = mTemplateData; + int startHeight = data.template_offset; + if (startHeight < 0) startHeight += 64; // 64 为世界底部高度, +64为了正确指向buffer + int totalHeight = startHeight + data.template_height; // 起点 + buffer 高度 + + for (auto& [key, buffer] : mBlockBuffer) { + mBlockVolume[string(key)] = BlockVolume(volume); + + auto& v = mBlockVolume[key]; + + v.mHeight = totalHeight; + v.mBlocks.mBegin = &*buffer.begin(); + v.mBlocks.mEnd = &*buffer.end(); + } + return true; +} + +int TemplateManager::getCurrentTemplateVersion() { return mTemplateData.version; } +int TemplateManager::getCurrentTemplateChunkNum() { return mTemplateData.template_chunk_num; } +int TemplateManager::getCurrentTemplateRoadWidth() { return mTemplateData.template_road_width; } +bool TemplateManager::isUseTemplate() { return config::cfg.generator.type == config::PlotGeneratorType::Template; } + +// Tools +void TemplateManager::toPositive(int& num) { + if (num < 0) num = -num; +} +string TemplateManager::calculateChunkID(const ChunkPos& pos) { + int n = getCurrentTemplateChunkNum(); + int x = ((pos.x % n) + n) % n; + int z = ((pos.z % n) + n) % n; + return fmt::format("({},{})", x, z); +} + + +// Record +bool TemplateManager::isRecordTemplateing() { return mIsRecording; } +bool TemplateManager::canRecordTemplate() { return !mIsRecording; } + + +bool TemplateManager::prepareRecordTemplate( + int stratY, // = offset + int endY, // = offset + height + int roadWidth, // = roadWidth + bool fillBedrock, + string const& defaultBlock +) { + if (mIsRecording) return false; + if (stratY >= endY) return false; + + mRecordData.template_offset = stratY; + mRecordData.template_height = endY - stratY; + mRecordData.template_road_width = roadWidth; + mRecordData.fill_bedrock = fillBedrock; + mRecordData.default_block = string{defaultBlock}; + + mIsRecording = true; + return true; +} +bool TemplateManager::postRecordTemplateStart(const ChunkPos& start) { + if (!mIsRecording) return false; + if (start.x != 0 && start.z != 0) return false; // 限定 (0,0) 开始 + + mRecordStart = ChunkPos{start}; + + return true; +} +bool TemplateManager::postRecordTemplateEnd(const ChunkPos& end) { + if (!mIsRecording) return false; + if (end.x != end.z) return false; // 限定正方形 + if (end.x <= 0 || end.z <= 0) return false; // 禁止2、3、4象限 + + mRecordEnd = ChunkPos{end}; + mRecordData.template_chunk_num = end.x + 1; // +1 因为 (0,0) 开始 + + mCanRecord = true; + + return true; +} + + +bool TemplateManager::_processChunk(const LevelChunk& chunk) { + auto const& min = chunk.getMin(); + auto const& bs = chunk.getDimension().getBlockSourceFromMainChunkSource(); + + // 获取当前区块的 buffer + auto& map = mRecordBlockIDMap; + auto& data = mRecordData; + auto& buffer = data.template_data[calculateChunkID(chunk.getPosition())]; + + + int totalHeight = data.template_offset + data.template_height + 64; // y 偏移量 + buffer 高度 + if (data.template_offset > 0) totalHeight += 64; // + 原版-64 + int const totalVolume = totalHeight * 16 * 16; // buffer 体积 16 * 16 * totalHeight + + buffer.reserve(totalVolume); // 预分配空间 + + + int const height = data.template_offset + data.template_height; // -64 + 16 = 48 + + BlockPos cur(min); + cur.y = mRecordData.template_offset; // 初始 y 轴 + for (int _x = 0; _x < 16; _x++) { + for (int _z = 0; _z < 16; _z++) { + for (int _y = mRecordData.template_offset; _y < height; _y++) { + cur.y = _y; + // printf("cur: (%d,%d,%d)\n", cur.x, cur.y, cur.z); + auto& bl = bs.getBlock(cur).getTypeName(); + auto iter = map.find(bl); + if (iter == map.end()) { + map[bl] = map.size() + 1; + buffer.push_back(map[bl]); + } else { + buffer.push_back(iter->second); + } + } + cur.z++; + } + cur.x++; + cur.z = min.z; // 重置 z 轴 + } + + return true; +} +bool TemplateManager::postRecordAndSaveTemplate(const string& fileName, Player& player) { + if (!mCanRecord) return false; + + mCanRecord = false; + mRecordData.block_map.clear(); + mRecordData.template_data.clear(); + + auto const& bs = player.getDimensionBlockSourceConst(); + + // 处理区块 + for (int x = mRecordStart.x; x <= mRecordEnd.x; x++) { + for (int z = mRecordStart.z; z <= mRecordEnd.z; z++) { + // printf("Processing chunk (%d,%d)\n", x, z); + auto ch = bs.getChunk(x, z); + if (!ch) { + sendText(player, "[TemplateManager] Can't get chunk ({},{}).", x, z); + break; + } + if (!ch->isFullyLoaded()) { + sendText(player, "[TemplateManager] Chunk ({},{}) is not fully loaded.", x, z); + break; + } + if (_processChunk(*ch)) { + sendText(player, "[TemplateManager] Process chunk ({},{}).", x, z); + } else { + sendText(player, "[TemplateManager] Can't process chunk ({},{}).", x, z); + } + } + } + + auto& idMap = mRecordData.block_map; + idMap.reserve(mRecordBlockIDMap.size()); + for (auto& [name, id] : mRecordBlockIDMap) { + idMap.emplace(std::to_string(id), name); // id -> name + } + + auto& data = mRecordData; + auto out = my_plugin::MyPlugin::getInstance().getSelf().getConfigDir() / fileName; + + std::ofstream ofs(out.string()); + if (ofs.is_open()) { + ofs << utils::JsonHelper::structToJsonString(data); + ofs.close(); + } + sendText(player, "[TemplateManager] Template saved to {}", out.string()); + + return true; +} +bool TemplateManager::resetRecordTemplate() { + mRecordData = {}; + mIsRecording = false; + mCanRecord = false; + mRecordStart = {}; + mRecordEnd = {}; + mRecordBlockIDMap.clear(); + return true; +} + +} // namespace plo::core \ No newline at end of file diff --git a/src/plotcraft/core/TemplateManager.h b/src/plotcraft/core/TemplateManager.h new file mode 100644 index 0000000..3ef4054 --- /dev/null +++ b/src/plotcraft/core/TemplateManager.h @@ -0,0 +1,87 @@ +#include "mc/world/actor/player/Player.h" +#include "mc/world/level/Level.h" +#include "mc/world/level/block/BlockVolume.h" +#include "mc/world/level/chunk/LevelChunk.h" +#include +#include +#include + + +using string = std::string; + +namespace plo::core { + +typedef string ints; +struct TemplateData { + int version{2}; // 模板版本 + int template_chunk_num{2}; // 模板区块数量 + int template_road_width{2}; // 模板道路宽度 + int template_height{16}; // 模板高度 + int template_offset{0}; // 模板偏移 + bool fill_bedrock{true}; // 是否填充基岩 + string default_block{"minecraft:air"}; // 默认方块 + + std::unordered_map block_map; // 方块映射 key: hashID + std::unordered_map> template_data; // 模板数据 key: chunkID +}; + + +class TemplateManager { +public: + // Template + static TemplateData mTemplateData; // 模板数据 + static std::unordered_map mBlockMap; // 方块映射 key: typeName + static std::unordered_map> mBlockBuffer; // 方块缓冲区 key: chunkID + static std::unordered_map mBlockVolume; // 方块体积 key: chunkID + + static bool loadTemplate(string const& path); + static bool _parseTemplate(); + + static bool generatorBlockVolume(BlockVolume& volume); + + static bool isUseTemplate(); + static int getCurrentTemplateChunkNum(); + static int getCurrentTemplateVersion(); + static int getCurrentTemplateRoadWidth(); + + // Record Template + static TemplateData mRecordData; // 记录模板数据 + static bool mIsRecording; // 是否正在记录模板 + static bool mCanRecord; // 是否可以记录模板 + static ChunkPos mRecordStart; // 记录模板开始 + static ChunkPos mRecordEnd; // 记录模板结束 + static std::unordered_map mRecordBlockIDMap; // 记录方块ID映射 key: typeName + + static bool canRecordTemplate(); // 是否可以记录模板 + static bool isRecordTemplateing(); // 是否正在记录模板 + + static bool prepareRecordTemplate( // 准备记录模板 + int stratY, // = offset + int endY, // = offset + height + int roadWidth, // = roadWidth + bool fillBedrock, + string const& defaultBlock + ); + + static bool postRecordTemplateStart(ChunkPos const& start); // 记录模板开始 + static bool postRecordTemplateEnd(ChunkPos const& end); // 记录模板结束 + + static bool postRecordAndSaveTemplate(string const& fileName, Player& player); // 记录并保存模板 + + static bool resetRecordTemplate(); // 重置记录模板 + + static bool _processChunk(LevelChunk const& chunk); // 处理区块 + + + // Tool functions + static void toPositive(int& num); // 将数字转换为正数 + static string calculateChunkID(ChunkPos const& pos); // 计算区块ID + +private: + TemplateManager() = delete; + ~TemplateManager() = delete; + TemplateManager(TemplateManager const&) = delete; + TemplateManager& operator=(TemplateManager const&) = delete; +}; + +} // namespace plo::core diff --git a/src/plotcraft/core/CoreUtils.cc b/src/plotcraft/core/Utils.cc similarity index 54% rename from src/plotcraft/core/CoreUtils.cc rename to src/plotcraft/core/Utils.cc index fad22fd..396de32 100644 --- a/src/plotcraft/core/CoreUtils.cc +++ b/src/plotcraft/core/Utils.cc @@ -1,11 +1,7 @@ -#include "CoreUtils.h" +#include "plotcraft/core/Utils.h" #include "mc/world/level/dimension/VanillaDimensions.h" -#include "mc/world/level/levelgen/WorldGenerator.h" -#include - - -namespace plo::core_utils { +namespace plo::core { int getPlotDimensionId() { #if defined(OVERWORLD) @@ -15,5 +11,4 @@ int getPlotDimensionId() { #endif } - -} // namespace plo::core_utils \ No newline at end of file +} // namespace plo::core \ No newline at end of file diff --git a/src/plotcraft/data/PlayerNameDB.cc b/src/plotcraft/data/PlayerNameDB.cc index 05f892b..dd3e4a0 100644 --- a/src/plotcraft/data/PlayerNameDB.cc +++ b/src/plotcraft/data/PlayerNameDB.cc @@ -8,22 +8,22 @@ PlayerNameDB& PlayerNameDB::getInstance() { return instance; } bool PlayerNameDB::initPlayerNameDB() { - if (isInit) return true; + if (mPlayerNameDB != nullptr) return true; mPlayerNameDB = std::make_unique( my_plugin::MyPlugin::getInstance().getSelf().getDataDir() / "PlayerNameDB" ); - return isInit = true; + return true; } bool PlayerNameDB::hasPlayer(string const& realName) { return mPlayerNameDB->has(realName); } bool PlayerNameDB::hasPlayer(mce::UUID const& uuid) { return mPlayerNameDB->has(uuid.asString()); } -string PlayerNameDB::getPlayerName(UUID const& uuid) { +string PlayerNameDB::getPlayerName(UUIDs const& uuid) { auto fn = mPlayerNameDB->get(uuid); if (fn) return *fn; return ""; } -UUID PlayerNameDB::getPlayerUUID(string const& realName) { +UUIDs PlayerNameDB::getPlayerUUID(string const& realName) { if (hasPlayer(realName)) return *mPlayerNameDB->get(realName); return ""; } diff --git a/src/plotcraft/data/PlotBDStorage.cc b/src/plotcraft/data/PlotBDStorage.cc deleted file mode 100644 index 3b7c712..0000000 --- a/src/plotcraft/data/PlotBDStorage.cc +++ /dev/null @@ -1,365 +0,0 @@ -#include "plotcraft/data/PlotBDStorage.h" -#include "SQLiteCpp/Database.h" -#include "ll/api/data/KeyValueDB.h" -#include "nlohmann/json_fwd.hpp" -#include "plotcraft/utils/Date.h" -#include "plotcraft/utils/JsonHelper.h" -#include "plugin/MyPlugin.h" -#include -#include -#include -#include -#include - - -#include "SQLiteCpp/SQLiteCpp.h" - -#ifdef DEBUG -#define debugger(...) std::cout << "[Debug] " << __VA_ARGS__ << std::endl; -#else -#define debugger(...) ((void)0) -#endif - -using namespace plo::utils; - -namespace plo::data { - -void PlotBDStorage::tryStartSaveThread() { - static bool isStarted = false; - if (isStarted) return; - isStarted = true; - std::thread([this]() { - while (true) { - debugger(" [" << Date{}.toString() << "] Saveing..."); - this->save(); - debugger(" [" << Date{}.toString() << "] Save done."); - std::this_thread::sleep_for(std::chrono::minutes(2)); - } - }).detach(); -} - -PlotBDStorage& PlotBDStorage::getInstance() { - static PlotBDStorage instance; - return instance; -} - -ll::data::KeyValueDB& PlotBDStorage::getDB() { return *mDB; } - - -#define DB_PlayerSettingsKey "PlayerSettings" -#define DB_PlotAdminsKey "PlotAdmins" - -void PlotBDStorage::load() { - if (!mDB) { - mDB = std::make_unique( - my_plugin::MyPlugin::getInstance().getSelf().getDataDir() / "PlotDBStorage" - ); - } - mAdmins.clear(); - mPlots.clear(); - initKey(); - tryConvertOldDB(); - - // Load data from database - auto* logger = &my_plugin::MyPlugin::getInstance().getSelf().getLogger(); - mDB->iter([this, logger](std::string_view key, std::string_view value) { - try { - if (key.starts_with("(") && key.ends_with(")")) { - auto j = nlohmann::json::parse(value); - auto ptr = PlotMetadata::make(); - JsonHelper::jsonToStruct(j, *ptr); - this->mPlots[ptr->getPlotID()] = ptr; - - } else if (key == DB_PlotAdminsKey) { - auto j = nlohmann::json::parse(value); - for (auto const& uuid : j) { - this->mAdmins.push_back(UUID(uuid.get())); - } - - } else if (key == DB_PlayerSettingsKey) { - auto j = nlohmann::json::parse(value); - for (auto const& [uuid, settings] : j.items()) { - PlayerSettingItem it; - JsonHelper::jsonToStruct(settings, it); - mPlayerSettings[UUID(uuid)] = it; - } - - } else logger->warn("Unknown key in PlotBDStorage: {}", key); - } catch (std::exception const& e) { - logger->error("Fail in {}, error key: {}\n{}", __func__, key, e.what()); - } catch (...) { - logger->fatal("Fail in {}, error key: {}", __func__, key); - } - return true; - }); - - logger->info("已加载 {} 条地皮数据", mPlots.size()); - logger->info("已加载 {} 位管理员数据", mAdmins.size()); - logger->info("已加载 {} 位玩家设置数据", mPlayerSettings.size()); -} - -void PlotBDStorage::save(PlotMetadata const& plot) { - auto j = JsonHelper::structToJson(plot); - mDB->set(plot.getPlotID(), j.dump()); -} - -void PlotBDStorage::save() { - // Save PlotMetadata - for (auto const& [id, ptr] : mPlots) { - save(*ptr); - } - - // Save PlotAdmins - mDB->set(DB_PlotAdminsKey, JsonHelper::structToJson(mAdmins).dump()); - - // Save PlayerSettings - { - nlohmann::json j = nlohmann::json::object(); - for (auto const& [uuid, settings] : mPlayerSettings) { - j[uuid] = JsonHelper::structToJson(settings); - } - mDB->set(DB_PlayerSettingsKey, j.dump()); - } -} - -void PlotBDStorage::initKey() { - if (!mDB->has(DB_PlayerSettingsKey)) { - mDB->set(DB_PlayerSettingsKey, "{}"); - } - if (!mDB->has(DB_PlotAdminsKey)) { - mDB->set(DB_PlotAdminsKey, "[]"); - } -} - -namespace fs = std::filesystem; - -void PlotBDStorage::tryConvertOldDB() { - auto& mSelf = my_plugin::MyPlugin::getInstance().getSelf(); - auto& logger = mSelf.getLogger(); - - fs::path oldSQLitePath = mSelf.getDataDir() / "PlotCraft.db"; - fs::path reNamedSQLitePath = mSelf.getDataDir() / "PlotCraft.db.bak"; - if (!fs::exists(oldSQLitePath)) return; - fs::rename(oldSQLitePath, reNamedSQLitePath); - - logger.warn("[Convert] 检测到旧数据库文件,尝试转换到LevelDB..."); - - try { - SQLite::Database sptr(reNamedSQLitePath, SQLite::OPEN_CREATE | SQLite::OPEN_READWRITE); - - // PlotAdmins - SQLite::Statement query(sptr, "SELECT * FROM PlotAdmins"); - while (query.executeStep()) { - auto uuid = query.getColumn(0).getText(); - mAdmins.push_back(UUID(uuid)); - } - logger.info("[Convert] [PlotAdmins] 转换完成,共计 {} 条数据。", mAdmins.size()); - - // Plots - SQLite::Statement query2(sptr, "SELECT * FROM Plots"); - while (query2.executeStep()) { - auto id = query2.getColumn(0).getText(); - auto name = query2.getColumn(1).getText(); - auto owner = query2.getColumn(2).getText(); - auto x = query2.getColumn(3).getInt(); - auto z = query2.getColumn(4).getInt(); - auto ptr = PlotMetadata::make(id, owner, name, x, z); - mPlots[id] = ptr; - } - logger.info("[Convert] [Plots] 转换完成,共计 {} 条数据。", mPlots.size()); - - // PlotShares - SQLite::Statement query3(sptr, "SELECT * FROM PlotShares"); - while (query3.executeStep()) { - auto plotID = query3.getColumn(0).getText(); - auto sharedPlayer = query3.getColumn(1).getText(); - auto sharedTime = query3.getColumn(2).getText(); - - auto ptr = getPlot(plotID); - if (ptr) { - ptr->mSharedPlayers.push_back({sharedPlayer, sharedTime}); - } else logger.warn("[Convert] [PlotShares] 未找到 {} 对应的地皮实例。", plotID); - } - logger.info("[Convert] [PlotShares] 转换完成,共计 {} 条数据。", mPlots.size()); - - // PlotCommenets - SQLite::Statement query4(sptr, "SELECT * FROM PlotCommenets"); - while (query4.executeStep()) { - auto id = query4.getColumn(0).getInt(); - auto plotID = query4.getColumn(1).getText(); - auto player = query4.getColumn(2).getText(); - auto time = query4.getColumn(3).getText(); - auto content = query4.getColumn(4).getText(); - - auto ptr = getPlot(plotID); - if (ptr) { - ptr->mComments.push_back({id, player, time, content}); - } else logger.warn("[Convert] [PlotCommenets] 未找到 {} 对应的地皮实例。", plotID); - } - logger.info("[Convert] [PlotCommenets] 转换完成,共计 {} 条数据。", mPlots.size()); - - // PlotSales - SQLite::Statement query5(sptr, "SELECT * FROM PlotSales"); - while (query5.executeStep()) { - auto plotID = query5.getColumn(0).getText(); - auto price = query5.getColumn(1).getInt(); - // auto time = query5.getColumn(2); // 废弃 - - auto ptr = getPlot(plotID); - if (ptr) { - ptr->setSaleStatus(true, price); - } else logger.warn("[Convert] [PlotSales] 未找到 {} 对应的地皮实例。", plotID); - } - logger.info("[Convert] [PlotSales] 转换完成,共计 {} 条数据。", mPlots.size()); - - logger.info("[Convert] 转换完成,旧数据库文件已备份至 {}", reNamedSQLitePath); - } catch (SQLite::Exception const& e) { - logger.error("[Convert] 转换旧数据库失败,SQLite::Exception: {}", e.what()); - } catch (std::exception const& e) { - logger.error("[Convert] 转换旧数据库失败,std::exception: {}", e.what()); - } catch (...) { - logger.error("[Convert] 转换旧数据库失败,未知错误"); - } -} - - -bool PlotBDStorage::hasAdmin(UUID const& uuid) const { - return std::find(mAdmins.begin(), mAdmins.end(), uuid) != mAdmins.end(); -} - -bool PlotBDStorage::isAdmin(UUID const& uuid) const { return hasAdmin(uuid); } - -bool PlotBDStorage::addAdmin(UUID const& uuid) { - if (hasAdmin(uuid)) { - return false; - } - mAdmins.push_back(UUID(uuid)); - return true; -} - -bool PlotBDStorage::delAdmin(UUID const& uuid) { - auto it = std::find(mAdmins.begin(), mAdmins.end(), uuid); - if (it == mAdmins.end()) { - return false; - } - mAdmins.erase(it); - return true; -} - -std::vector PlotBDStorage::getAdmins() const { return mAdmins; } - - -bool PlotBDStorage::hasPlot(PlotID const& id) const { return mPlots.find(id) != mPlots.end(); } - -bool PlotBDStorage::delPlot(PlotID const& id) { - auto it = mPlots.find(id); - if (it == mPlots.end()) { - return false; - } - mPlots.erase(it); - return true; -} - -bool PlotBDStorage::addPlot(PlotMetadataPtr ptr) { - if (hasPlot(ptr->getPlotID())) { - return false; - } - if (ptr->getPlotOwner().empty() || ptr->getPlotID().empty()) { - throw std::runtime_error("Invalid plot metadata"); - return false; - } - - mPlots[ptr->getPlotID()] = ptr; - return true; -} - -bool PlotBDStorage::addPlot(PlotID const& id, UUID const& owner, int x, int z) { - auto ptr = PlotMetadata::make(id, owner, x, z); - return addPlot(ptr); -} - -PlotMetadataPtr PlotBDStorage::getPlot(PlotID const& id) const { - auto it = mPlots.find(id); - if (it == mPlots.end()) { - return nullptr; - } - return it->second; -} - -std::vector PlotBDStorage::getPlots() const { - std::vector res; - for (auto const& [id, ptr] : mPlots) { - res.push_back(ptr); - } - return res; -} - -std::vector PlotBDStorage::getPlots(UUID const& owner) const { - std::vector res; - for (auto const& [id, ptr] : mPlots) { - if (ptr->getPlotOwner() == owner) { - res.push_back(ptr); - } - } - return res; -} - - -bool PlotBDStorage::hasPlayerSetting(UUID const& uuid) const { - return mPlayerSettings.find(uuid) != mPlayerSettings.end(); -} -bool PlotBDStorage::initPlayerSetting(UUID const& uuid) { - if (hasPlayerSetting(uuid)) { - return false; - } - mPlayerSettings[uuid] = PlayerSettingItem{}; - return true; -} -bool PlotBDStorage::setPlayerSetting(UUID const& uuid, PlayerSettingItem const& setting) { - if (!hasPlayerSetting(uuid)) { - return false; - } - mPlayerSettings[uuid] = PlayerSettingItem{setting}; // copy - return true; -} -PlayerSettingItem PlotBDStorage::getPlayerSetting(UUID const& uuid) const { - auto it = mPlayerSettings.find(uuid); - if (it == mPlayerSettings.end()) { - return PlayerSettingItem{}; - } - return it->second; -} - - -std::vector PlotBDStorage::getSaleingPlots() const { - std::vector res; - for (auto const& [id, ptr] : mPlots) { - if (ptr->isSale()) { - res.push_back(ptr); - } - } - return res; -} - -bool PlotBDStorage::buyPlotFromSale(PlotID const& id, UUID const& buyer, bool resetShares) { - auto ptr = getPlot(id); - if (!ptr) return false; - - ptr->setPlotOwner(buyer); - ptr->setSaleStatus(false, 0); - if (resetShares) ptr->resetSharedPlayers(); - - return true; -} - -PlotPermission PlotBDStorage::getPlayerPermission(UUID const& uuid, PlotID const& id, bool ignoreAdmin) const { - if (!ignoreAdmin && isAdmin(uuid)) return PlotPermission::Admin; - - auto ptr = getPlot(id); - if (!ptr) return PlotPermission::None; - - return ptr->getPlayerInThisPlotPermission(uuid); -} - - -} // namespace plo::data \ No newline at end of file diff --git a/src/plotcraft/data/PlotDBStorage.cc b/src/plotcraft/data/PlotDBStorage.cc new file mode 100644 index 0000000..cb14c10 --- /dev/null +++ b/src/plotcraft/data/PlotDBStorage.cc @@ -0,0 +1,254 @@ +#include "plotcraft/data/PlotDBStorage.h" +#include "ll/api/data/KeyValueDB.h" +#include "nlohmann/json_fwd.hpp" +#include "plotcraft/utils/Date.h" +#include "plotcraft/utils/JsonHelper.h" +#include "plugin/MyPlugin.h" +#include +#include +#include +#include + + +#include "plotcraft/utils/Debugger.h" + + +using namespace plo::utils; + +namespace plo::data { + +void PlotDBStorage::tryStartSaveThread() { + static bool isStarted = false; + if (isStarted) return; + isStarted = true; + std::thread([this]() { + while (true) { + debugger("[" << Date{}.toString() << "] Saveing..."); + this->save(); + debugger("[" << Date{}.toString() << "] Save done."); + std::this_thread::sleep_for(std::chrono::minutes(2)); + } + }).detach(); +} + +ll::data::KeyValueDB& PlotDBStorage::getDB() { return *mDB; } +PlotDBStorage& PlotDBStorage::getInstance() { + static PlotDBStorage instance; + return instance; +} + + +#define DB_PlayerSettingsKey "PlayerSettings" +#define DB_PlotAdminsKey "PlotAdmins" +#define DB_ArchivedPrefix "Archived_" // Archived_(0,1) + +void PlotDBStorage::_initKey() { + if (!mDB->has(DB_PlayerSettingsKey)) { + mDB->set(DB_PlayerSettingsKey, "{}"); + } + if (!mDB->has(DB_PlotAdminsKey)) { + mDB->set(DB_PlotAdminsKey, "[]"); + } +} + + +void PlotDBStorage::load() { + if (!mDB) { + mDB = std::make_unique( + my_plugin::MyPlugin::getInstance().getSelf().getDataDir() / "PlotDBStorage" + ); + } + mAdmins.clear(); + mPlots.clear(); + _initKey(); + + // Load data from database + auto* logger = &my_plugin::MyPlugin::getInstance().getSelf().getLogger(); + mDB->iter([this, logger](std::string_view key, std::string_view value) { + try { + if (key.starts_with("(") && key.ends_with(")")) { + auto j = nlohmann::json::parse(value); + auto ptr = PlotMetadata::make(); + JsonHelper::jsonToStruct(j, *ptr); + this->mPlots[ptr->getPlotID()] = ptr; + + } else if (key == DB_PlotAdminsKey) { + auto j = nlohmann::json::parse(value); + JsonHelper::jsonToStructNoMerge(j, mAdmins); + + } else if (key == DB_PlayerSettingsKey) { + auto j = nlohmann::json::parse(value); + for (auto const& [uuid, settings] : j.items()) { + PlayerSettingItem it; + JsonHelper::jsonToStruct(settings, it); // load and merge fix values + mPlayerSettings[UUIDs(uuid)] = it; + } + } + } catch (std::exception const& e) { + logger->error("Fail in {}, error key: {}\n{}", __func__, key, e.what()); + } catch (...) { + logger->fatal("Fail in {}, error key: {}", __func__, key); + } + return true; + }); + + logger->info("已加载 {}条 地皮数据", mPlots.size()); + logger->info("已加载 {}位 管理员数据", mAdmins.size()); + logger->info("已加载 {}位 玩家设置数据", mPlayerSettings.size()); +} + +void PlotDBStorage::save(PlotMetadata const& plot) { + mDB->set(plot.getPlotID(), JsonHelper::structToJson(plot).dump()); +} +void PlotDBStorage::save() { + // PlotMetadata + for (auto const& [id, ptr] : mPlots) save(*ptr); + + // PlotAdmins + mDB->set(DB_PlotAdminsKey, JsonHelper::structToJson(mAdmins).dump()); + + // PlayerSettings + mDB->set(DB_PlayerSettingsKey, JsonHelper::structToJson(mPlayerSettings).dump()); +} + + +bool PlotDBStorage::hasAdmin(UUIDs const& uuid) const { + return std::find(mAdmins.begin(), mAdmins.end(), uuid) != mAdmins.end(); +} +bool PlotDBStorage::isAdmin(UUIDs const& uuid) const { return hasAdmin(uuid); } + +bool PlotDBStorage::addAdmin(UUIDs const& uuid) { + if (hasAdmin(uuid)) { + return false; + } + mAdmins.push_back(UUIDs(uuid)); + return true; +} + +bool PlotDBStorage::delAdmin(UUIDs const& uuid) { + auto it = std::find(mAdmins.begin(), mAdmins.end(), uuid); + if (it == mAdmins.end()) { + return false; + } + mAdmins.erase(it); + return true; +} + +std::vector PlotDBStorage::getAdmins() const { return mAdmins; } + + +// Plots +bool PlotDBStorage::hasPlot(PlotID const& id) const { return mPlots.find(id) != mPlots.end(); } + +bool PlotDBStorage::delPlot(PlotID const& id) { + auto it = mPlots.find(id); + if (it == mPlots.end()) { + return false; + } + mPlots.erase(it); + return true; +} + +bool PlotDBStorage::addPlot(PlotMetadataPtr ptr) { + if (hasPlot(ptr->getPlotID())) { + return false; + } + if (ptr->getPlotOwner().empty() || ptr->getPlotID().empty()) { + throw std::runtime_error("Invalid plot metadata"); + return false; + } + + mPlots[ptr->getPlotID()] = ptr; + return true; +} + +bool PlotDBStorage::addPlot(PlotID const& id, UUIDs const& owner, int x, int z) { + auto ptr = PlotMetadata::make(id, owner, x, z); + return addPlot(ptr); +} + +PlotMetadataPtr PlotDBStorage::getPlot(PlotID const& id) const { + auto it = mPlots.find(id); + if (it == mPlots.end()) { + return nullptr; + } + return it->second; +} + +std::vector PlotDBStorage::getPlots() const { + std::vector res; + for (auto const& [id, ptr] : mPlots) { + res.push_back(ptr); + } + return res; +} + +std::vector PlotDBStorage::getPlots(UUIDs const& owner) const { + std::vector res; + for (auto const& [id, ptr] : mPlots) { + if (ptr->getPlotOwner() == owner) { + res.push_back(ptr); + } + } + return res; +} + + +bool PlotDBStorage::hasPlayerSetting(UUIDs const& uuid) const { + return mPlayerSettings.find(uuid) != mPlayerSettings.end(); +} +bool PlotDBStorage::initPlayerSetting(UUIDs const& uuid) { + if (hasPlayerSetting(uuid)) { + return false; + } + mPlayerSettings[uuid] = PlayerSettingItem{}; + return true; +} +bool PlotDBStorage::setPlayerSetting(UUIDs const& uuid, PlayerSettingItem const& setting) { + if (!hasPlayerSetting(uuid)) { + return false; + } + mPlayerSettings[uuid] = PlayerSettingItem{setting}; // copy + return true; +} +PlayerSettingItem PlotDBStorage::getPlayerSetting(UUIDs const& uuid) const { + auto it = mPlayerSettings.find(uuid); + if (it == mPlayerSettings.end()) { + return PlayerSettingItem{}; + } + return it->second; +} + + +std::vector PlotDBStorage::getSaleingPlots() const { + std::vector res; + for (auto const& [id, ptr] : mPlots) { + if (ptr->isSale()) { + res.push_back(ptr); + } + } + return res; +} + +bool PlotDBStorage::buyPlotFromSale(PlotID const& id, UUIDs const& buyer, bool resetShares) { + auto ptr = getPlot(id); + if (!ptr) return false; + + ptr->setPlotOwner(buyer); + ptr->setSaleStatus(false, 0); + if (resetShares) ptr->resetSharedPlayers(); + + return true; +} + +PlotPermission PlotDBStorage::getPlayerPermission(UUIDs const& uuid, PlotID const& id, bool ignoreAdmin) const { + if (!ignoreAdmin && isAdmin(uuid)) return PlotPermission::Admin; + + auto ptr = getPlot(id); + if (!ptr) return PlotPermission::None; + + return ptr->getPlayerInThisPlotPermission(uuid); +} + + +} // namespace plo::data \ No newline at end of file diff --git a/src/plotcraft/data/PlotMetadata.cc b/src/plotcraft/data/PlotMetadata.cc index a7a19d5..92a7f85 100644 --- a/src/plotcraft/data/PlotMetadata.cc +++ b/src/plotcraft/data/PlotMetadata.cc @@ -1,5 +1,5 @@ #include "plotcraft/data/PlotMetadata.h" -#include "plotcraft/data/PlotBDStorage.h" +#include "plotcraft/data/PlotDBStorage.h" #include "plotcraft/utils/Date.h" #include "plotcraft/utils/JsonHelper.h" #include @@ -12,23 +12,26 @@ using namespace plo::utils; namespace plo::data { -PlotMetadataPtr PlotMetadata::make(PlotID const& id, UUID const& owner, string const& name, int x, int z) { + +// 构造 +PlotMetadataPtr PlotMetadata::make(PlotID const& id, UUIDs const& owner, string const& name, int x, int z) { auto ptr = std::make_shared(); ptr->mPlotID = string(id); // 拷贝 ptr->mPlotName = string(name); ptr->mPlotX = int(x); ptr->mPlotZ = int(z); - ptr->mPlotOwner = UUID(owner); + ptr->mPlotOwner = UUIDs(owner); return ptr; } -PlotMetadataPtr PlotMetadata::make(PlotID const& id, UUID const& owner, int x, int z) { +PlotMetadataPtr PlotMetadata::make(PlotID const& id, UUIDs const& owner, int x, int z) { return make(id, owner, "", x, z); } -PlotMetadataPtr PlotMetadata::make(PlotID const& id, int x, int z) { return make(id, UUID{}, "", x, z); } -PlotMetadataPtr PlotMetadata::make() { return make(PlotID{}, UUID{}, "", 0, 0); } +PlotMetadataPtr PlotMetadata::make(PlotID const& id, int x, int z) { return make(id, UUIDs{}, "", x, z); } +PlotMetadataPtr PlotMetadata::make() { return make(PlotID{}, UUIDs{}, "", 0, 0); } -bool PlotMetadata::isOwner(UUID const& uuid) const { return mPlotOwner == uuid; } +// API +bool PlotMetadata::isOwner(UUIDs const& uuid) const { return mPlotOwner == uuid; } bool PlotMetadata::setPlotName(string const& name) { mPlotName = string(name); @@ -51,13 +54,13 @@ bool PlotMetadata::setZ(int z) { return true; } -bool PlotMetadata::setPlotOwner(UUID const& uuid) { +bool PlotMetadata::setPlotOwner(UUIDs const& uuid) { if (uuid.empty()) return false; - mPlotOwner = UUID(uuid); + mPlotOwner = UUIDs(uuid); return true; } -bool PlotMetadata::isSharedPlayer(UUID const& uuid) const { +bool PlotMetadata::isSharedPlayer(UUIDs const& uuid) const { return std::find_if( mSharedPlayers.begin(), mSharedPlayers.end(), @@ -66,13 +69,13 @@ bool PlotMetadata::isSharedPlayer(UUID const& uuid) const { != mSharedPlayers.end(); } -bool PlotMetadata::addSharedPlayer(UUID const& uuid) { +bool PlotMetadata::addSharedPlayer(UUIDs const& uuid) { if (isSharedPlayer(uuid)) return false; mSharedPlayers.emplace_back(PlotShareItem{uuid, Date{}.toString()}); return true; } -bool PlotMetadata::delSharedPlayer(UUID const& uuid) { +bool PlotMetadata::delSharedPlayer(UUIDs const& uuid) { auto it = std::find_if(mSharedPlayers.begin(), mSharedPlayers.end(), [uuid](auto const& p) { return p.mSharedPlayer == uuid; }); @@ -94,14 +97,14 @@ bool PlotMetadata::hasComment(CommentID const& id) const { != mComments.end(); } -bool PlotMetadata::isCommentOwner(CommentID const& id, UUID const& uuid) const { +bool PlotMetadata::isCommentOwner(CommentID const& id, UUIDs const& uuid) const { auto opt = getComment(id); if (!opt.has_value()) return false; return opt->mCommentPlayer == uuid; } -bool PlotMetadata::addComment(UUID const& uuid, string const& text) { +bool PlotMetadata::addComment(UUIDs const& uuid, string const& text) { if (text.empty()) return false; mComments.emplace_back(PlotCommentItem{static_cast(mComments.size()) + 1, uuid, Date{}.toString(), text}); return true; @@ -134,7 +137,7 @@ std::optional PlotMetadata::getComment(CommentID const& id) con std::vector PlotMetadata::getComments() const { return mComments; } -std::vector PlotMetadata::getComments(UUID const& uuid) const { +std::vector PlotMetadata::getComments(UUIDs const& uuid) const { std::vector res; std::copy_if(mComments.begin(), mComments.end(), std::back_inserter(res), [uuid](auto const& c) { return c.mCommentPlayer == uuid; @@ -166,14 +169,14 @@ PlotID PlotMetadata::getPlotID() const { return mPlotID; } string PlotMetadata::getPlotName() const { return mPlotName; } -UUID PlotMetadata::getPlotOwner() const { return mPlotOwner; } +UUIDs PlotMetadata::getPlotOwner() const { return mPlotOwner; } int PlotMetadata::getX() const { return mPlotX; } int PlotMetadata::getZ() const { return mPlotZ; } -PlotPermission PlotMetadata::getPlayerInThisPlotPermission(UUID const& uuid) const { - if (mPlotOwner == uuid) return PlotPermission::Owner; +PlotPermission PlotMetadata::getPlayerInThisPlotPermission(UUIDs const& uuid) const { + if (isOwner(uuid)) return PlotPermission::Owner; if (isSharedPlayer(uuid)) return PlotPermission::Shared; return PlotPermission::None; } @@ -183,7 +186,7 @@ PlotPermissionTable& PlotMetadata::getPermissionTable() { return mPermissi PlotPermissionTable const& PlotMetadata::getPermissionTableConst() const { return mPermissionTable; } -void PlotMetadata::save() { PlotBDStorage::getInstance().save(*this); } +void PlotMetadata::save() { PlotDBStorage::getInstance().save(*this); } string PlotMetadata::toString() const { return JsonHelper::structToJson(*this).dump(); } diff --git a/src/plotcraft/event/Event.cc b/src/plotcraft/event/Event.cc index ed59500..a3b135c 100644 --- a/src/plotcraft/event/Event.cc +++ b/src/plotcraft/event/Event.cc @@ -1,7 +1,4 @@ #include "Event.h" -#include "fmt/format.h" -#include "ll/api/chrono/GameChrono.h" -#include "ll/api/event/Event.h" #include "ll/api/event/EventBus.h" #include "ll/api/event/Listener.h" #include "ll/api/event/ListenerBase.h" @@ -14,14 +11,10 @@ #include "ll/api/event/player/PlayerUseItemEvent.h" #include "ll/api/event/world/FireSpreadEvent.h" #include "ll/api/event/world/SpawnMobEvent.h" -#include "ll/api/schedule/Scheduler.h" -#include "ll/api/schedule/Task.h" #include "ll/api/service/Bedrock.h" #include "mc/common/wrapper/optional_ref.h" #include "mc/enums/BlockUpdateFlag.h" #include "mc/enums/GameType.h" -#include "mc/enums/TextPacketType.h" -#include "mc/network/packet/TextPacket.h" #include "mc/network/packet/UpdateBlockPacket.h" #include "mc/server/ServerPlayer.h" #include "mc/world/actor/player/Player.h" @@ -29,176 +22,84 @@ #include "mc/world/level/Level.h" #include "mc/world/level/block/Block.h" #include "mc/world/level/dimension/Dimension.h" -#include "mc/world/level/dimension/VanillaDimensions.h" #include "mc/world/level/material/Material.h" #include "mc/world/phys/HitResult.h" #include "plotcraft/Config.h" #include "plotcraft/EconomyQueue.h" -#include "plotcraft/PlotPos.h" -#include "plotcraft/core/CoreUtils.h" +#include "plotcraft/core/PPos.h" +#include "plotcraft/core/Utils.h" #include "plotcraft/data/PlayerNameDB.h" -#include "plotcraft/data/PlotBDStorage.h" +#include "plotcraft/data/PlotDBStorage.h" #include "plotcraft/data/PlotMetadata.h" #include "plotcraft/event/PlotEvents.h" #include "plotcraft/utils/Mc.h" -#include "plotcraft/utils/Text.h" #include "plotcraft/utils/Utils.h" #include "plugin/MyPlugin.h" #include #include -#include -#include +#include "EventHook.h" +#include "RuntimeMap.h" +#include "Scheduler.h" +#include "plotcraft/utils/Debugger.h" + +#include "plotcraft/event/hook/ArmorStandSwapItemEvent.h" +#include "plotcraft/event/hook/PlayerAttackBlockEvent.h" +#include "plotcraft/event/hook/PlayerDropItemEvent.h" #include "plotcraft/event/hook/SculkBlockGrowthEvent.h" #include "plotcraft/event/hook/SculkSpreadEvent.h" -#ifdef DEBUG -#define debugger(...) std::cout << "[Debug] " << __VA_ARGS__ << std::endl; -#else -#define debugger(...) ((void)0) -#endif - - -using string = std::string; -using ll::chrono_literals::operator""_tick; +using string = std::string; using PlotPermission = plo::data::PlotPermission; -class CustomEventHelper { -public: - // player PlotPos Dimension - std::unordered_map> mPlayerPos; // 玩家位置缓存 - - bool has(string const& uid) { return mPlayerPos.find(uid) != mPlayerPos.end(); } - - void set(string uid, plo::PlotPos pps, int dim) { mPlayerPos[uid] = std::make_pair(pps, dim); } - - std::pair get(string const& uid) { - auto it = mPlayerPos.find(uid); - if (it == mPlayerPos.end()) return std::make_pair(plo::PlotPos{}, -1); - return it->second; - } -} helper; - // Global variables -ll::schedule::GameTickScheduler mTickScheduler; // Tick调度 -ll::event::ListenerPtr mPlayerJoinEventListener; // 玩家进入服务器 -ll::event::ListenerPtr mSpawningMobEventListener; // 生物尝试生成 -ll::event::ListenerPtr mPlayerDestroyBlockEventListener; // 玩家尝试破坏方块 -ll::event::ListenerPtr mPlayerPlaceingBlockEventListener; // 玩家尝试放置方块 -ll::event::ListenerPtr mPlayerUseItemOnEventListener; // 玩家对方块使用物品(点击右键) -ll::event::ListenerPtr mFireSpreadEventListener; // 火焰蔓延 -ll::event::ListenerPtr mPlayerAttackEventListener; // 玩家攻击实体 -ll::event::ListenerPtr mPlayerPickUpItemEventListener; // 玩家捡起物品 -ll::event::ListenerPtr mPlayerInteractBlockEventListener; // 方块接受玩家互动 -ll::event::ListenerPtr mSculkSpreadEventListener; // 幽匿脉络蔓延 -ll::event::ListenerPtr mSculkBlockGrowthEventListener; // 幽匿方块生长(幽匿[尖啸/感测]体) -ll::event::ListenerPtr mPlayerUseItemEventListener; // 玩家使用物品 - -ll::event::ListenerPtr mPlayerLeavePlotEventListener; // 玩家离开地皮 -ll::event::ListenerPtr mPlayerEnterPlotEventListener; // 玩家进入地皮 +ll::event::ListenerPtr mPlayerJoinEvent; // 玩家进入服务器 +ll::event::ListenerPtr mSpawningMobEvent; // 生物尝试生成 +ll::event::ListenerPtr mPlayerDestroyBlockEvent; // 玩家尝试破坏方块 +ll::event::ListenerPtr mPlayerPlaceingBlockEvent; // 玩家尝试放置方块 +ll::event::ListenerPtr mPlayerUseItemOnEvent; // 玩家对方块使用物品(点击右键) +ll::event::ListenerPtr mFireSpreadEvent; // 火焰蔓延 +ll::event::ListenerPtr mPlayerAttackEntityEvent; // 玩家攻击实体 +ll::event::ListenerPtr mPlayerPickUpItemEvent; // 玩家捡起物品 +ll::event::ListenerPtr mPlayerInteractBlockEvent; // 方块接受玩家互动 +ll::event::ListenerPtr mSculkSpreadEvent; // 幽匿脉络蔓延 +ll::event::ListenerPtr mPlayerLeavePlotEvent; // 玩家离开地皮 +ll::event::ListenerPtr mPlayerEnterPlotEvent; // 玩家进入地皮 +ll::event::ListenerPtr mSculkBlockGrowthEvent; // 幽匿方块生长(幽匿[尖啸/感测]体) +ll::event::ListenerPtr mPlayerUseItemEvent; // 玩家使用物品 +ll::event::ListenerPtr mArmorStandSwapItemEvent; // 玩家交换盔甲架物品 +ll::event::ListenerPtr mPlayerAttackBlockEvent; // 玩家攻击方块 +ll::event::ListenerPtr mPlayerDropItemEvent; // 玩家丢弃物品 namespace plo::event { +using namespace core; -using namespace core_utils; - -void buildTipMessage(Player& p, PlotPos const& pps, PlayerNameDB* ndb, PlotBDStorage* pdb) { - try { - PlotMetadataPtr plot = pdb->getPlot(pps.getPlotID()); - if (plot == nullptr) plot = PlotMetadata::make(pps.getPlotID(), pps.x, pps.z); - - TextPacket pkt = TextPacket(); - pkt.mType = TextPacketType::Tip; - if (pps.isValid()) { - auto owner = plot->getPlotOwner(); - // clang-format off - pkt.mMessage = fmt::format( - "地皮: {0}\n主人: {1} | 名称: {2}\n出售: {3} | 价格: {4}{5}", - pps.toDebug(), - owner.empty() ? "无主" : ndb->getPlayerName(owner), - plot->getPlotName(), - owner.empty() ? "§a✔§r" : plot->isSale() ? "§a✔§r" : "§c✘§r", - owner.empty() ? config::cfg.plotWorld.buyPlotPrice : plot->isSale() ? plot->getSalePrice() : 0, - owner.empty() ? "\n输入:/plo plot 打开购买菜单" : "" - ); - // clang-format on - } else pkt.mMessage = fmt::format("{0} | 地皮世界\n输入: /plo 打开地皮菜单", PLUGIN_TITLE); // Tip3 - - p.sendNetworkPacket(pkt); - } catch (std::exception const& e) { - my_plugin::MyPlugin::getInstance().getSelf().getLogger().error( - "Fail in {}\nstd::exception: {}", - __FUNCTION__, - e.what() - ); - } catch (...) { - my_plugin::MyPlugin::getInstance().getSelf().getLogger().error("Fail in {}\nunknown exception", __FUNCTION__); - } +bool CheckPerm(PlotDBStorage* pdb, PlotID const& id, UUIDs const& uuid, bool ignoreAdmin) { + PlotDBStorage* db = pdb ? pdb : &PlotDBStorage::getInstance(); + return db->getPlayerPermission(uuid, id, ignoreAdmin) != PlotPermission::None; } +bool StringFind(string const& str, string const& sub) { return str.rfind(sub) != string::npos; } bool registerEventListener() { auto* bus = &ll::event::EventBus::getInstance(); - auto* pdb = &data::PlotBDStorage::getInstance(); + auto* pdb = &data::PlotDBStorage::getInstance(); auto* ndb = &data::PlayerNameDB::getInstance(); - // Tick scheduler => 处理自定义事件 - mTickScheduler.add(4_tick, [bus, pdb, ndb]() { - auto lv = ll::service::getLevel(); - if (!lv) return; // nullptr - - lv->forEachPlayer([bus, pdb, ndb](Player& p) { - if (p.isSimulatedPlayer() || p.isLoading()) return true; // skip simulated player - - int const playerDimid = p.getDimensionId(); - int const plotDimid = getPlotDimensionId(); - auto const name = p.getRealName(); - auto const pair = helper.get(name); - PlotPos const pps{p.getPosition()}; - - if (playerDimid != plotDimid) { - // 玩家通过传送离开地皮维度 - // 地皮维度 => 其它维度 触发 - // 其它维度 => 地皮维度 忽略 - // 其它维度 => 其它维度 忽略 - if (pair.second != -1 && pair.second != playerDimid && pair.second == plotDimid) { - debugger("离开地皮(维度): " << pps.toDebug()); - bus->publish(PlayerLeavePlot(pair.first, &p)); - helper.set(name, pps, playerDimid); - } - return true; - } - - if (pdb->getPlayerSetting(p.getUuid().asString()).showPlotTip) buildTipMessage(p, pps, ndb, pdb); - bool const valid = pps.isValid(); - - // Leave - if ((!valid && pair.first != pps) || (valid && pair.first != pps)) { - debugger("离开地皮(移动): " << pair.first.toDebug()); - bus->publish(PlayerLeavePlot(pair.first, &p)); // use last position - } - // Join - if (valid && pair.first != pps) { - debugger("进入地皮: " << pps.toDebug()); - bus->publish(PlayerEnterPlot(pps, &p)); - } - helper.set(name, pps, playerDimid); - return true; - }); - }); - // My events + initPlotEventScheduler(); // 初始化地皮事件调度器 if (config::cfg.plotWorld.inPlotCanFly) { - mPlayerEnterPlotEventListener = bus->emplaceListener([pdb](PlayerEnterPlot& e) { + mPlayerEnterPlotEvent = bus->emplaceListener([pdb](PlayerEnterPlot& e) { auto pl = e.getPlayer(); if (pl == nullptr) return; auto const gamemode = pl->getPlayerGameType(); if (gamemode == GameType::Creative || gamemode == GameType::Spectator) return; // 不处理创造模式和旁观模式 - auto pps = PlotPos(pl->getPosition()); + auto pps = PPos(pl->getPosition()); auto level = pdb->getPlayerPermission(pl->getUuid().asString(), pps.toString(), true); if (level == PlotPermission::Owner || level == PlotPermission::Shared) { @@ -207,7 +108,7 @@ bool registerEventListener() { } }); - mPlayerLeavePlotEventListener = bus->emplaceListener([](PlayerLeavePlot& e) { + mPlayerLeavePlotEvent = bus->emplaceListener([](PlayerLeavePlot& e) { auto pl = e.getPlayer(); if (pl == nullptr) return; @@ -221,237 +122,351 @@ bool registerEventListener() { }); } - // Minecraft events - mPlayerJoinEventListener = - bus->emplaceListener([ndb, pdb](ll::event::PlayerJoinEvent& e) { - if (e.self().isSimulatedPlayer()) return true; // skip simulated player - ndb->insertPlayer(e.self()); - EconomyQueue::getInstance().transfer(e.self()); - pdb->initPlayerSetting(e.self().getUuid().asString()); - return true; - }); + registerHook(); + mPlayerJoinEvent = bus->emplaceListener([ndb, pdb](ll::event::PlayerJoinEvent& e) { + if (e.self().isSimulatedPlayer()) return true; // skip simulated player + ndb->insertPlayer(e.self()); + EconomyQueue::getInstance().transfer(e.self()); + pdb->initPlayerSetting(e.self().getUuid().asString()); + return true; + }); - mPlayerDestroyBlockEventListener = + mPlayerDestroyBlockEvent = bus->emplaceListener([pdb](ll::event::PlayerDestroyBlockEvent& ev) { auto& player = ev.self(); if (player.getDimensionId() != getPlotDimensionId()) return true; // 被破坏的方块不在地皮世界 auto const& blockPos = ev.pos(); - auto plotPos = PlotPos(blockPos); - auto const lv = pdb->getPlayerPermission(player.getUuid().asString(), plotPos.toString()); + auto pps = PPos(blockPos); - debugger("破坏方块: " << blockPos.toString() << ", 权限: " << std::to_string(static_cast(lv))); + debugger("[DestroyBlock]: " << blockPos.toString()); - if (lv == PlotPermission::Admin) return true; // 放行管理员 + if (CheckPerm(pdb, pps.getPlotID(), player.getUuid().asString(), false)) return true; - auto const meta = pdb->getPlot(plotPos.getPlotID()); + auto const meta = pdb->getPlot(pps.getPlotID()); if (meta) { - bool const valid = plotPos.isValid(); - bool const border = plotPos.isPosOnBorder(blockPos); - auto const& tab = meta->getPermissionTableConst(); - - if (!valid || border) ev.cancel(); // 破坏道路 / 边框 - if (valid && !border && lv == PlotPermission::None && !tab.canDestroyBlock) - ev.cancel(); // 访客破坏地皮内方块 - } else ev.cancel(); // 破坏未分配的地皮方块 + auto const& tab = meta->getPermissionTableConst(); + + if (pps.isValid() && !pps.isPosOnBorder(blockPos) && tab.allowDestroy) + return true; // 玩家有权限破坏地皮内方块 + } + + ev.cancel(); return true; }); - mPlayerPlaceingBlockEventListener = + mPlayerPlaceingBlockEvent = bus->emplaceListener([pdb](ll::event::PlayerPlacingBlockEvent& ev) { auto& player = ev.self(); if (player.getDimensionId() != getPlotDimensionId()) return true; auto const& blockPos = mc::face2Pos(ev.pos(), ev.face()); // 计算实际放置位置 - auto pps = PlotPos(blockPos); - auto const lv = pdb->getPlayerPermission(player.getUuid().asString(), pps.toString()); + auto pps = PPos(blockPos); - debugger("放置方块: " << blockPos.toString() << ", 权限: " << std::to_string(static_cast(lv))); + debugger("[PlaceingBlock]: " << blockPos.toString()); - if (lv == PlotPermission::Admin) return true; + if (CheckPerm(pdb, pps.getPlotID(), player.getUuid().asString())) return true; auto const meta = pdb->getPlot(pps.getPlotID()); if (meta) { - bool const valid = pps.isValid(); - bool const border = pps.isPosOnBorder(blockPos); - auto const& tab = meta->getPermissionTableConst(); + auto const& tab = meta->getPermissionTableConst(); + + if (pps.isValid() && !pps.isPosOnBorder(blockPos) && tab.allowPlace) return true; + } - if (!valid || border) ev.cancel(); - if (valid && !border && lv == PlotPermission::None && !tab.canPlaceBlock) ev.cancel(); - } else ev.cancel(); + ev.cancel(); return true; }); - mPlayerUseItemOnEventListener = + mPlayerUseItemOnEvent = bus->emplaceListener([pdb](ll::event::PlayerInteractBlockEvent& e) { auto& player = e.self(); if (player.getDimensionId() != getPlotDimensionId()) return true; - auto const& vec3 = e.clickPos(); - auto pps = PlotPos(vec3); - auto const lv = pdb->getPlayerPermission(player.getUuid().asString(), pps.toString()); - bool const valid = pps.isValid(); + auto const& vec3 = e.clickPos(); + auto pps = PPos(vec3); + + debugger("[UseItemOn]: " << e.item().getTypeName() << ", 位置: " << vec3.toString()); - debugger( - "使用物品: " << e.item().getTypeName() << ", 位置: " << vec3.toString() - << ", 权限: " << std::to_string(static_cast(lv)) - ); + if (!pps.isValid()) return true; // 不在地皮上 + if (CheckPerm(pdb, pps.getPlotID(), player.getUuid().asString())) return true; - if (utils::some(config::cfg.plotWorld.eventListener.onUseItemOnWhiteList, e.item().getTypeName())) - return true; // 白名单物品 - if (lv == PlotPermission::Admin || !valid) return true; // 管理员或地皮外 + auto const& bt = e.block()->getTypeName(); + auto const& it = e.item().getTypeName(); + if (!RuntimeMap::has(MapType::UseItemOn, bt) && !RuntimeMap::has(MapType::UseItemOn, it)) { + return true; + } auto const meta = pdb->getPlot(pps.getPlotID()); if (meta) { - bool const border = pps.isPosOnBorder(vec3); - auto const& tab = meta->getPermissionTableConst(); + // clang-format off + auto const& tab = meta->getPermissionTableConst(); + if (StringFind(it, "bucket") && tab.useBucket) return true; // 各种桶 + if (StringFind(it, "axe") && tab.allowAxePeeled) return true; // 斧头给木头去皮 + if (StringFind(it, "hoe") && tab.useHoe) return true; // 锄头耕地 + if (StringFind(it, "_shovel") && tab.useShovel) return true; // 锹铲除草径 + if (it == "minecraft:skull" && tab.allowPlace) return true; // 放置头颅 + if (it == "minecraft:banner" && tab.allowPlace) return true; // 放置旗帜 + if (it == "minecraft:glow_ink_sac" && tab.allowPlace) return true; // 发光墨囊给木牌上色 + if (it == "minecraft:end_crystal" && tab.allowPlace) return true; // 末地水晶 + if (it == "minecraft:ender_eye" && tab.allowPlace) return true; // 放置末影之眼 + if (it == "minecraft:flint_and_steel" && tab.useFiregen) return true; // 使用打火石 + if (it == "minecraft:bone_meal" && tab.useBoneMeal) return true; // 使用骨粉 + + if (StringFind(bt, "button") && tab.useButton) return true; // 各种按钮 + if (bt == "minecraft:dragon_egg" && tab.allowAttackDragonEgg) return true; // 右键龙蛋 + if (bt == "minecraft:bed" && tab.useBed) return true; // 床 + if ((bt == "minecraft:chest" || bt == "minecraft:trapped_chest") && tab.allowOpenChest) return true; // 箱子&陷阱箱 + if (bt == "minecraft:crafting_table" && tab.useCraftingTable) return true; // 工作台 + if ((bt == "minecraft:campfire" || bt == "minecraft:soul_campfire") && tab.useCampfire) return true; // 营火(烧烤) + if (bt == "minecraft:composter" && tab.useComposter) return true; // 堆肥桶(放置肥料) + if (StringFind(bt,"shulker_box") && tab.useShulkerBox) return true; // 潜影盒 + if (bt == "minecraft:noteblock" && tab.useNoteBlock) return true; // 音符盒(调音) + if (bt == "minecraft:jukebox" && tab.useJukebox) return true; // 唱片机(放置/取出唱片) + if (bt == "minecraft:bell" && tab.useBell) return true; // 钟(敲钟) + if ((bt == "minecraft:daylight_detector_inverted" || bt == "minecraft:daylight_detector") && tab.useDaylightDetector) return true; // 光线传感器(切换日夜模式) + if (bt == "minecraft:lectern" && tab.useLectern) return true; // 讲台 + if (bt == "minecraft:cauldron" && tab.useCauldron) return true; // 炼药锅 + if (bt == "minecraft:lever" && tab.useLever) return true; // 拉杆 + if (bt == "minecraft:respawn_anchor" && tab.useRespawnAnchor) return true; // 重生锚(充能) + if (StringFind(bt, "_door") && tab.useDoor) return true; // 各种门 + if (StringFind(bt, "fence_gate") && tab.useFenceGate) return true; // 各种栏栅门 + if (StringFind(bt, "trapdoor") && tab.useTrapdoor) return true; // 各种活板门 + if (bt == "minecraft:flower_pot" && tab.editFlowerPot) return true; // 花盆 + if (StringFind(bt, "_sign") && tab.editSign) return true; // 编辑告示牌 + // clang-format on + } - if (border) e.cancel(); - if (valid && !border && lv == PlotPermission::None && !tab.canUseItemOn) e.cancel(); - } else e.cancel(); + e.cancel(); return true; }); - mFireSpreadEventListener = bus->emplaceListener([pdb](ll::event::FireSpreadEvent& e) { - auto const& pos = e.pos(); - auto pps = PlotPos(pos); - if (pps.isValid()) { - auto const meta = pdb->getPlot(pps.getPlotID()); - if (meta) { - if (!meta->getPermissionTableConst().canFireSpread) e.cancel(); - } - } else e.cancel(); // 地皮外 + mFireSpreadEvent = bus->emplaceListener([pdb](ll::event::FireSpreadEvent& e) { + auto const& pos = e.pos(); + auto pps = PPos(pos); + auto const meta = pdb->getPlot(pps.getPlotID()); + + if (meta) { + if (pps.isValid() && meta->getPermissionTableConst().allowFireSpread) return true; + } + + e.cancel(); return true; }); - mPlayerAttackEventListener = + mPlayerAttackEntityEvent = bus->emplaceListener([pdb](ll::event::PlayerAttackEvent& e) { auto& player = e.self(); if (player.getDimensionId() != getPlotDimensionId()) return true; auto const& pos = e.target().getPosition(); - auto pps = PlotPos(pos); - auto const lv = pdb->getPlayerPermission(player.getUuid().asString(), pps.toString()); + auto pps = PPos(pos); - debugger( - "玩家攻击: " << e.target().getEntityLocNameString() << ", 位置: " << pos.toString() - << ", 权限: " << std::to_string(static_cast(lv)) - ); + debugger("[AttackEntity]: " << e.target().getTypeName() << ", 位置: " << pos.toString()); - bool const valid = pps.isValid(); - if (lv == PlotPermission::Admin || !valid) return true; // 管理员或地皮外 + if (!pps.isValid()) return true; + if (pdb->isAdmin(player.getUuid().asString())) return true; - auto const meta = pdb->getPlot(pps.getPlotID()); - if (meta && lv == PlotPermission::None && !meta->getPermissionTableConst().canAttack) e.cancel(); + auto const& et = e.target().getTypeName(); + auto const& tab = pdb->getPlot(pps.getPlotID())->getPermissionTableConst(); + if (et == "minecraft:ender_crystal" && tab.allowAttackEnderCrystal) return true; // 末影水晶 + if (et == "minecraft:armor_stand" && tab.allowDestroyArmorStand) return true; // 盔甲架 + if (tab.allowAttackPlayer && e.target().isPlayer()) return true; // 玩家 + if (tab.allowAttackAnimal && RuntimeMap::has(MapType::AnimalEntity, et)) return true; // 动物 + if (tab.allowAttackMob && RuntimeMap::has(MapType::MobEntity, et)) return true; // 怪物 + + if (CheckPerm(pdb, pps.getPlotID(), player.getUuid().asString(), true)) return true; + + e.cancel(); return true; }); - mPlayerPickUpItemEventListener = + mPlayerPickUpItemEvent = bus->emplaceListener([pdb](ll::event::PlayerPickUpItemEvent& e) { auto& player = e.self(); if (player.getDimensionId() != getPlotDimensionId()) return true; auto const& pos = e.itemActor().getPosition(); - auto pps = PlotPos(pos); - auto const lv = pdb->getPlayerPermission(player.getUuid().asString(), pps.toString()); + auto pps = PPos(pos); - debugger( - "玩家捡起物品: " << e.itemActor().getEntityLocNameString() << ", 位置: " << pos.toString() - << ", 权限: " << std::to_string(static_cast(lv)) - ); + debugger("[PickUpItem]: " << e.itemActor().getTypeName() << ", 位置: " << pos.toString()); - bool const valid = pps.isValid(); - if (lv == PlotPermission::Admin || !valid) return true; // 管理员或地皮外 + if (!pps.isValid()) return true; + if (pdb->isAdmin(player.getUuid().asString())) return true; - auto const meta = pdb->getPlot(pps.getPlotID()); - if (meta) { - if (valid && lv == PlotPermission::None && !meta->getPermissionTableConst().canPickupItem) e.cancel(); - } + auto const& tab = pdb->getPlot(pps.getPlotID())->getPermissionTableConst(); + if (tab.allowPickupItem) return true; + + if (CheckPerm(pdb, pps.getPlotID(), player.getUuid().asString(), true)) return true; + + e.cancel(); return true; }); - mPlayerInteractBlockEventListener = + mPlayerInteractBlockEvent = bus->emplaceListener([pdb](ll::event::PlayerInteractBlockEvent& e) { auto& player = e.self(); if (player.getDimensionId() != getPlotDimensionId()) return true; + auto const& pos = e.blockPos(); // 交互的方块位置 - auto pps = PlotPos(pos); - auto const lv = pdb->getPlayerPermission(player.getUuid().asString(), pps.toString()); + auto pps = PPos(pos); + auto const& bn = e.block()->getTypeName(); + + debugger("[InteractBlock]: " << pos.toString()); + + if (!pps.isValid()) return true; + if (!RuntimeMap::has(MapType::InteractBlock, bn)) return true; + if (CheckPerm(pdb, pps.getPlotID(), player.getUuid().asString())) return true; + + auto const& tab = pdb->getPlot(pps.getPlotID())->getPermissionTableConst(); + + if (bn == "minecraft:cartography_table" && tab.useCartographyTable) return true; // 制图台 + if (bn == "minecraft:smithing_table" && tab.useSmithingTable) return true; // 锻造台 + if (bn == "minecraft:brewing_stand" && tab.useBrewingStand) return true; // 酿造台 + if (bn == "minecraft:anvil" && tab.useAnvil) return true; // 铁砧 + if (bn == "minecraft:grindstone" && tab.useGrindstone) return true; // 磨石 + if (bn == "minecraft:enchanting_table" && tab.useEnchantingTable) return true; // 附魔台 + if (bn == "minecraft:barrel" && tab.useBarrel) return true; // 桶 + if (bn == "minecraft:beacon" && tab.useBeacon) return true; // 信标 + if (bn == "minecraft:hopper" && tab.useHopper) return true; // 漏斗 + if (bn == "minecraft:dropper" && tab.useDropper) return true; // 投掷器 + if (bn == "minecraft:dispenser" && tab.useDispenser) return true; // 发射器 + if (bn == "minecraft:loom" && tab.useLoom) return true; // 织布机 + if (bn == "minecraft:stonecutter_block" && tab.useStonecutter) return true; // 切石机 + if (StringFind(bn, "blast_furnace") && tab.useBlastFurnace) return true; // 高炉 + if (StringFind(bn, "furnace") && tab.useFurnace) return true; // 熔炉 + if (StringFind(bn, "smoker") && tab.useSmoker) return true; // 烟熏炉 + + e.cancel(); + return true; + }); + + mPlayerUseItemEvent = bus->emplaceListener([](ll::event::PlayerUseItemEvent& ev) { + if (ev.self().getDimensionId() != getPlotDimensionId()) return true; + if (!StringFind(ev.item().getTypeName(), "bucket")) return true; + + auto& player = ev.self(); + auto val = player.traceRay(5.5f, false, true, [&](BlockSource const&, Block const& bl, bool) { + // if (!bl.isSolid()) return false; // 非固体方块 + if (bl.getMaterial().isLiquid()) return false; // 液体方块 + return true; + }); - debugger("玩家交互方块: " << pos.toString() << ", 权限: " << std::to_string(static_cast(lv))); + BlockPos const& pos = val.mBlockPos; + ItemStack const& item = ev.item(); + Block const& bl = player.getDimensionBlockSource().getBlock(pos); + + debugger("[UseItem]: " << item.getTypeName() << ", 位置: " << pos.toString() << ", 方块: " << bl.getTypeName()); + + auto pps = PPos(pos); + if (pps.isValid() && pps.isPosOnBorder(pos)) { + ev.cancel(); + UpdateBlockPacket( + pos, + (uint)UpdateBlockPacket::BlockLayer::Extra, + bl.getBlockItemId(), + (uchar)BlockUpdateFlag::All + ) + .sendTo(player); // 防刁民在边框放水,导致客户端不更新 + return true; + }; + return true; + }); - if (lv == PlotPermission::Admin || !pps.isValid()) return true; // 管理员或地皮外 + mPlayerAttackBlockEvent = + bus->emplaceListener([](more_events::PlayerAttackBlockEvent& ev) { + optional_ref pl = ev.getPlayer(); + if (!pl.has_value()) return true; + Player& player = pl.value(); - auto const meta = pdb->getPlot(pps.getPlotID()); + if (player.getDimensionId() != getPlotDimensionId()) return true; + + debugger("[AttackBlock]: Pos: " << ev.getPos().toString()); + + auto pps = PPos(ev.getPos()); + if (!pps.isValid()) return true; + if (CheckPerm(nullptr, pps.getPlotID(), player.getUuid().asString())) return true; + + auto const& bl = player.getDimensionBlockSourceConst().getBlock(ev.getPos()).getTypeName(); + auto const meta = PlotDBStorage::getInstance().getPlot(pps.getPlotID()); if (meta) { - if (lv == PlotPermission::None && !meta->getPermissionTableConst().canInteractBlock) e.cancel(); + if (meta->getPermissionTableConst().allowAttackDragonEgg && bl == "minecraft:dragon_egg") return true; } + + ev.cancel(); return true; }); - mPlayerUseItemEventListener = - bus->emplaceListener([](ll::event::PlayerUseItemEvent& ev) { - if (ev.self().getDimensionId() != getPlotDimensionId()) return true; - auto& player = ev.self(); + mArmorStandSwapItemEvent = + bus->emplaceListener([](more_events::ArmorStandSwapItemEvent& ev) { + Player& player = ev.getPlayer(); + if (player.getDimensionId() != getPlotDimensionId()) return true; - auto val = player.traceRay(5.5f, false, true, [&](BlockSource const&, Block const& bl, bool) { - // if (!bl.isSolid()) return false; // 非固体方块 - if (bl.getMaterial().isLiquid()) return false; // 液体方块 - return true; - }); + debugger("[ArmorStandSwapItem]: executed"); - auto const& item = ev.item(); - BlockPos const& pos = val.mBlockPos; - Block const& bl = player.getDimensionBlockSource().getBlock(pos); - - debugger( - "玩家使用物品: " << item.getTypeName() << ", 位置: " << pos.toString() << ", 方块: " << bl.getTypeName() - ); - - auto pps = PlotPos(pos); - bool const border = pps.isPosOnBorder(pos); - - if (border) { - ev.cancel(); - UpdateBlockPacket( - pos, - (uint)UpdateBlockPacket::BlockLayer::Extra, - bl.getBlockItemId(), - (uchar)BlockUpdateFlag::All - ) - .sendTo(player); // 防刁民在边框放水,然后客户端不更新 - return true; - }; + auto pps = PPos(player.getPosition()); + if (!pps.isValid()) return true; + if (CheckPerm(nullptr, pps.getPlotID(), player.getUuid().asString())) return true; + + auto const meta = PlotDBStorage::getInstance().getPlot(pps.getPlotID()); + if (meta) { + if (meta->getPermissionTableConst().useArmorStand) return true; + } + + ev.cancel(); return true; }); + mPlayerDropItemEvent = + bus->emplaceListener([](more_events::PlayerDropItemEvent& ev) { + Player& player = ev.getPlayer(); + if (player.getDimensionId() != getPlotDimensionId()) return true; - // 可开关事件监听器 - if (!config::cfg.plotWorld.spawnMob) { - mSpawningMobEventListener = - bus->emplaceListener([](ll::event::SpawningMobEvent& e) { - if (e.blockSource().getDimensionId() == getPlotDimensionId()) e.cancel(); // 拦截地皮世界生物生成 - return true; - }); - } + debugger("[PlayerDropItem]: executed"); + + auto pps = PPos(player.getPosition()); + if (!pps.isValid()) return true; + + if (CheckPerm(nullptr, pps.getPlotID(), player.getUuid().asString())) return true; + + auto const meta = PlotDBStorage::getInstance().getPlot(pps.getPlotID()); + if (meta) { + if (meta->getPermissionTableConst().allowDropItem) return true; + } + return true; + }); + + // 可开关事件(作用于地皮世界) + mSpawningMobEvent = bus->emplaceListener([](ll::event::SpawningMobEvent& e) { + if (e.blockSource().getDimensionId() != getPlotDimensionId()) return true; + + auto const& type = e.identifier().getFullName(); + + debugger("[SpawningMob]: " << type); + + if (config::cfg.plotWorld.spawnMob) return true; + + e.cancel(); + return true; + }); if (config::cfg.plotWorld.eventListener.onSculkSpreadListener) { - mSculkSpreadEventListener = bus->emplaceListener([](hook::SculkSpreadEvent& ev) { + mSculkSpreadEvent = bus->emplaceListener([](more_events::SculkSpreadEvent& ev) { auto bs = ev.getBlockSource(); - if (!bs.has_value()) return true; - if (bs->getDimensionId() == getPlotDimensionId()) ev.cancel(); // 地皮世界 + if (bs.has_value()) + if (bs->getDimensionId() == getPlotDimensionId()) ev.cancel(); // 地皮世界 return true; }); } if (config::cfg.plotWorld.eventListener.onSculkBlockGrowthListener) { - mSculkBlockGrowthEventListener = - bus->emplaceListener([](hook::SculkBlockGrowthEvent& ev) { - auto sou = ev.getSource(); - if (sou) - if (sou->getDimensionId() == getPlotDimensionId()) ev.cancel(); // 地皮世界 + mSculkBlockGrowthEvent = + bus->emplaceListener([](more_events::SculkBlockGrowthEvent& ev) { + auto bs = ev.getBlockSource(); + if (bs.has_value()) + if (bs->getDimensionId() == getPlotDimensionId()) ev.cancel(); // 地皮世界 return true; }); } @@ -460,21 +475,24 @@ bool registerEventListener() { bool unRegisterEventListener() { - mTickScheduler.clear(); - auto& bus = ll::event::EventBus::getInstance(); - bus.removeListener(mPlayerJoinEventListener); - bus.removeListener(mSpawningMobEventListener); - bus.removeListener(mPlayerDestroyBlockEventListener); - bus.removeListener(mPlayerPlaceingBlockEventListener); - bus.removeListener(mPlayerUseItemOnEventListener); - bus.removeListener(mFireSpreadEventListener); - bus.removeListener(mPlayerAttackEventListener); - bus.removeListener(mPlayerPickUpItemEventListener); - bus.removeListener(mPlayerInteractBlockEventListener); - bus.removeListener(mSculkSpreadEventListener); - bus.removeListener(mSculkBlockGrowthEventListener); - bus.removeListener(mPlayerUseItemEventListener); + bus.removeListener(mPlayerJoinEvent); + bus.removeListener(mSpawningMobEvent); + bus.removeListener(mPlayerDestroyBlockEvent); + bus.removeListener(mPlayerPlaceingBlockEvent); + bus.removeListener(mPlayerUseItemOnEvent); + bus.removeListener(mFireSpreadEvent); + bus.removeListener(mPlayerAttackEntityEvent); + bus.removeListener(mPlayerPickUpItemEvent); + bus.removeListener(mPlayerInteractBlockEvent); + bus.removeListener(mSculkSpreadEvent); + bus.removeListener(mSculkBlockGrowthEvent); + bus.removeListener(mPlayerUseItemEvent); + bus.removeListener(mPlayerAttackBlockEvent); + bus.removeListener(mArmorStandSwapItemEvent); + bus.removeListener(mPlayerDropItemEvent); + + unregisterHook(); return true; } diff --git a/src/plotcraft/event/Event.h b/src/plotcraft/event/Event.h index e1b0e4f..36946ad 100644 --- a/src/plotcraft/event/Event.h +++ b/src/plotcraft/event/Event.h @@ -1,3 +1,8 @@ +#pragma once +#include "plotcraft/data/PlotDBStorage.h" +#include "plotcraft/data/PlotMetadata.h" +#include + namespace plo::event { @@ -7,4 +12,8 @@ bool registerEventListener(); bool unRegisterEventListener(); +bool CheckPerm(data::PlotDBStorage* pdb, data::PlotID const& id, data::UUIDs const& uuid, bool ignoreAdmin = false); +bool StringFind(std::string const& str, std::string const& sub); + + } // namespace plo::event \ No newline at end of file diff --git a/src/plotcraft/event/EventHook.cc b/src/plotcraft/event/EventHook.cc new file mode 100644 index 0000000..614ef3b --- /dev/null +++ b/src/plotcraft/event/EventHook.cc @@ -0,0 +1,624 @@ +#include "ll/api/memory/Hook.h" +#include "ll/api/service/Bedrock.h" +#include "mc/entity/utilities/ActorDamageCause.h" +#include "mc/server/ServerPlayer.h" +#include "mc/server/commands/CommandOrigin.h" +#include "mc/server/commands/CommandOriginType.h" +#include "mc/world/ActorUniqueID.h" +#include "mc/world/actor/ActorDamageSource.h" +#include "mc/world/actor/Mob.h" +#include "mc/world/containers/ContainerID.h" +#include "mc/world/inventory/transaction/InventorySource.h" +#include "mc/world/scores/ScoreInfo.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Event.h" +#include "RuntimeMap.h" +#include "Scheduler.h" +#include "plotcraft/Config.h" +#include "plotcraft/EconomyQueue.h" +#include "plotcraft/core/PPos.h" +#include "plotcraft/core/Utils.h" +#include "plotcraft/data/PlayerNameDB.h" +#include "plotcraft/data/PlotDBStorage.h" +#include "plotcraft/data/PlotMetadata.h" +#include "plotcraft/event/PlotEvents.h" +#include "plotcraft/utils/Debugger.h" +#include "plotcraft/utils/Mc.h" +#include "plotcraft/utils/Utils.h" +#include "plugin/MyPlugin.h" + + +using string = std::string; +using PlotPermission = plo::data::PlotPermission; + +#define CATCH \ + catch (std::exception const& e) { \ + my_plugin::MyPlugin::getInstance().getSelf().getLogger().error( \ + "[Hook] Catch exception at {}: {}", \ + __FUNCTION__, \ + e.what() \ + ); \ + } \ + catch (...) { \ + my_plugin::MyPlugin::getInstance().getSelf().getLogger().error( \ + "[Hook] Catch unknown exception at {}", \ + __FUNCTION__ \ + ); \ + } +#define CATCH_RET(ret) \ + catch (std::exception const& e) { \ + my_plugin::MyPlugin::getInstance().getSelf().getLogger().error( \ + "[Hook] Catch exception at {}: {}", \ + __FUNCTION__, \ + e.what() \ + ); \ + return ret; \ + } \ + catch (...) { \ + my_plugin::MyPlugin::getInstance().getSelf().getLogger().error( \ + "[Hook] Catch unknown exception at {}", \ + __FUNCTION__ \ + ); \ + return ret; \ + } + +#define Normal High + +namespace plo::event { +using namespace core; + + +// 生物受伤 +LL_TYPE_INSTANCE_HOOK( + MobHurtEffectHook, + HookPriority::Normal, + Mob, + &Mob::getDamageAfterResistanceEffect, + float, + ActorDamageSource const& source, + float damage +) { + try { + if (this->getDimensionId() != getPlotDimensionId()) return origin(source, damage); + + debugger("[MobHurt] mob: " << this->getTypeName()); + + auto pps = PPos(this->getPosition()); + if (!pps.isValid()) return origin(source, damage); + + auto meta = PlotDBStorage::getInstance().getPlot(pps.getPlotID()); + if (meta) { + auto const& et = this->getTypeName(); + auto const& tab = meta->getPermissionTableConst(); + if (tab.allowAttackPlayer && this->isPlayer()) return origin(source, damage); + if (tab.allowAttackAnimal && RuntimeMap::has(MapType::AnimalEntity, et)) return origin(source, damage); + if (tab.allowAttackMob && RuntimeMap::has(MapType::MobEntity, et)) return origin(source, damage); + } + + if (this->isPlayer()) { + auto const pl = this->getWeakEntity().tryUnwrap(); + if (pl.has_value()) { + if (CheckPerm(nullptr, pps.getPlotID(), pl->getUuid().asString())) return origin(source, damage); + } + } + + return 0.0f; + } + CATCH_RET(0.0f); +} + + +// 耕地退化 +LL_TYPE_INSTANCE_HOOK( + FarmDecayHook, + HookPriority::Normal, + FarmBlock, + "?transformOnFall@FarmBlock@@UEBAXAEAVBlockSource@@AEBVBlockPos@@PEAVActor@@M@Z", + void, + BlockSource& region, + BlockPos const& pos, + Actor* actor, + float fallDistance +) { + try { + if (region.getDimensionId() != getPlotDimensionId()) return origin(region, pos, actor, fallDistance); + + debugger("[耕地退化] pos: " << pos.toString()); + + auto pps = PPos(pos); + if (!pps.isValid()) return origin(region, pos, actor, fallDistance); + + auto const meta = PlotDBStorage::getInstance().getPlot(pps.getPlotID()); + if (meta) { + if (meta->getPermissionTableConst().allowFarmDecay) return origin(region, pos, actor, fallDistance); + } + } + CATCH; +} + + +// 玩家操作物品展示框 +const auto UseFrameBlockCallback = [](Player& player, BlockPos const& pos) -> bool { + if (player.getDimensionId() != getPlotDimensionId()) return false; + + debugger("[物品展示框] pos: " << pos.toString()); + + auto pps = PPos(pos); + if (!pps.isValid()) return true; + + auto const meta = PlotDBStorage::getInstance().getPlot(pps.getPlotID()); + if (meta) { + if (meta->getPermissionTableConst().useItemFrame) return true; + } + if (CheckPerm(nullptr, pps.getPlotID(), player.getUuid().asString())) return true; + + return false; +}; +LL_TYPE_INSTANCE_HOOK( + PlayerUseFrameHook1, + HookPriority::Normal, + ItemFrameBlock, + "?use@ItemFrameBlock@@UEBA_NAEAVPlayer@@AEBVBlockPos@@E@Z", + bool, + Player& player, + BlockPos const& pos, + uchar face +) { + try { + if (UseFrameBlockCallback(player, pos)) return origin(player, pos, face); + return false; + } + CATCH_RET(false); +} +LL_TYPE_INSTANCE_HOOK( + PlayerUseFrameHook2, + HookPriority::Normal, + ItemFrameBlock, + "?attack@ItemFrameBlock@@UEBA_NPEAVPlayer@@AEBVBlockPos@@@Z", + bool, + Player* player, + BlockPos const& pos +) { + try { + if (UseFrameBlockCallback(*player, pos)) return origin(player, pos); + return false; + } + CATCH_RET(false); +} + + +// 弹射物生成 +const auto SpawnProjectileCallback = [](Actor* actor, string const& type) -> bool { + if (actor->getDimensionId() != getPlotDimensionId()) return false; + + debugger("[弹射物生成] type: " << type); + + auto pps = PPos(actor->getPosition()); + if (!pps.isValid()) return true; + + auto const meta = PlotDBStorage::getInstance().getPlot(pps.getPlotID()); + if (meta) { + auto const& tab = meta->getPermissionTableConst(); + if (type == "minecraft:fishing_hook" && tab.useFishingHook) return true; // 钓鱼竿 + if (type == "minecraft:splash_potion" && tab.allowThrowPotion) return true; // 喷溅药水 + if (type == "minecraft:lingering_potion" && tab.allowThrowPotion) return true; // 滞留药水 + if (type == "minecraft:thrown_trident" && tab.allowThrowTrident) return true; // 三叉戟 + if (type == "minecraft:arrow" && tab.allowShoot) return true; // 箭 + if (type == "minecraft:crossbow" && tab.allowShoot) return true; // 弩射烟花 + if (type == "minecraft:snowball" && tab.allowThrowSnowball) return true; // 雪球 + if (type == "minecraft:ender_pearl" && tab.allowThrowEnderPearl) return true; // 末影珍珠 + if (type == "minecraft:egg" && tab.allowThrowEgg) return true; // 鸡蛋 + } + + if (actor->isPlayer()) { + auto pl = actor->getWeakEntity().tryUnwrap(); + if (pl.has_value()) { + if (CheckPerm(nullptr, pps.getPlotID(), pl->getUuid().asString())) return true; + } + } + + return false; +}; +LL_TYPE_INSTANCE_HOOK( + ProjectileSpawnHook1, + HookPriority::Normal, + Spawner, + &Spawner::spawnProjectile, + Actor*, + BlockSource& region, + ActorDefinitionIdentifier const& id, + Actor* spawner, + Vec3 const& position, + Vec3 const& direction +) { + try { + if (SpawnProjectileCallback(spawner, id.getCanonicalName())) + return origin(region, id, spawner, position, direction); + return nullptr; + } + CATCH_RET(nullptr); +} +LL_TYPE_INSTANCE_HOOK( + ProjectileSpawnHook2, + HookPriority::Normal, + CrossbowItem, + &CrossbowItem::_shootFirework, + void, + ItemInstance const& projectileInstance, + Player& player +) { + try { + if (SpawnProjectileCallback(&player, projectileInstance.getTypeName())) + return origin(projectileInstance, player); + } + CATCH; +} +LL_TYPE_INSTANCE_HOOK( + ProjectileSpawnHook3, + HookPriority::Normal, + TridentItem, + "?releaseUsing@TridentItem@@UEBAXAEAVItemStack@@PEAVPlayer@@H@Z", + void, + ItemStack& item, + Player* player, + int durationLeft +) { + try { + if (SpawnProjectileCallback(player, VanillaActorRendererId::trident.getString())) + return origin(item, player, durationLeft); + } + CATCH; +} + + +// 生物踩踏压力板 +LL_TYPE_INSTANCE_HOOK( + PressurePlateTriggerHook, + HookPriority::Normal, + BasePressurePlateBlock, + "?shouldTriggerEntityInside@BasePressurePlateBlock@@UEBA_NAEAVBlockSource@@AEBVBlockPos@@AEAVActor@@@Z", + bool, + BlockSource& region, + BlockPos const& pos, + Actor& entity +) { + try { + if (region.getDimensionId() != getPlotDimensionId()) return origin(region, pos, entity); + + debugger("[压力板] pos: " << pos.toString() << " entity: " << entity.getTypeName()); + + auto pps = PPos(pos); + if (!pps.isValid()) return origin(region, pos, entity); + + auto const meta = PlotDBStorage::getInstance().getPlot(pps.getPlotID()); + if (meta) { + if (meta->getPermissionTableConst().usePressurePlate) return origin(region, pos, entity); + } + + if (entity.isPlayer()) { + auto pl = entity.getWeakEntity().tryUnwrap(); + if (pl.has_value()) { + if (CheckPerm(nullptr, pps.getPlotID(), pl->getUuid().asString())) return origin(region, pos, entity); + } + } + + return false; + } + CATCH_RET(false); +} + + +// 生物骑乘 +LL_TYPE_INSTANCE_HOOK( + ActorRideHook, + HookPriority::Normal, + Actor, + "?canAddPassenger@Actor@@UEBA_NAEAV1@@Z", + bool, + Actor& passenger +) { + try { + if (passenger.getDimensionId() != getPlotDimensionId()) return origin(passenger); + + debugger("[生物骑乘] executed!"); + + if (!passenger.isPlayer()) return origin(passenger); + + auto pps = PPos(passenger.getPosition()); + if (!pps.isValid()) return origin(passenger); + + auto const& type = this->getTypeName(); + auto const meta = PlotDBStorage::getInstance().getPlot(pps.getPlotID()); + if (meta) { + auto const& tab = meta->getPermissionTableConst(); + if (type == "minecraft:minecart" || type == "minecraft:boat") { + if (tab.allowRideTrans) return origin(passenger); + } else { + if (tab.allowRideEntity) return origin(passenger); + } + } + + if (passenger.isPlayer()) { + auto pl = passenger.getWeakEntity().tryUnwrap(); + if (pl.has_value()) { + if (CheckPerm(nullptr, pps.getPlotID(), pl->getUuid().asString())) return origin(passenger); + } + } + + return false; + } + CATCH_RET(false); +} + + +// 凋零破坏方块 +LL_TYPE_INSTANCE_HOOK( + WitherDestroyHook, + HookPriority::Normal, + WitherBoss, + &WitherBoss::_destroyBlocks, + void, + Level& level, + AABB const& bb, + BlockSource& region, + int range, + WitherBoss::WitherAttackType type +) { + try { + if (region.getDimensionId() != getPlotDimensionId()) return origin(level, bb, region, range, type); + + debugger("[凋零破坏方块] executed!"); + + Cube c(bb.min, bb.max); + auto land = c.getRangedPlots(); + auto& db = PlotDBStorage::getInstance(); + + for (auto const& p : land) { + if (p.isCubeOnBorder(c)) { + my_plugin::MyPlugin::getInstance().getSelf().getLogger().warn( + "Wither try to destroy block on border of plot {} at {}", + p.getPlotID(), + bb.toString() + ); + return; // 禁止破坏边框 + } + + auto meta = db.getPlot(p.getPlotID()); + if (meta) { + if (!meta->getPermissionTableConst().allowWitherDestroy) { + return; + } + } else return; // 如果地块不存在,则不进行破坏 + } + + return origin(level, bb, region, range, type); + } + CATCH; +} + + +// 活塞推动方块 +LL_TYPE_INSTANCE_HOOK( + PistonPushHook, + HookPriority::Normal, + PistonBlockActor, + &PistonBlockActor::_attachedBlockWalker, + bool, + BlockSource& region, + BlockPos const& curPos, // 被推的方块位置 + uchar curBranchFacing, + uchar pistonMoveFacing +) { + try { + if (region.getDimensionId() != getPlotDimensionId()) + return origin(region, curPos, curBranchFacing, pistonMoveFacing); + + debugger("[活塞推动方块] 目标: " << curPos.toString()); + + auto sou = PPos(this->getPosition()); + auto tar = PPos(curPos); + + if (sou.isValid() && tar.isValid() && sou == tar) { + if (!sou.isPosOnBorder(curPos) && !tar.isPosOnBorder(curPos)) { + auto const meta = PlotDBStorage::getInstance().getPlot(sou.getPlotID()); + if (meta) { + if (meta->getPermissionTableConst().allowPistonPush) + return origin(region, curPos, curBranchFacing, pistonMoveFacing); + } + } + } + + return false; + } + CATCH_RET(false); +} + + +// 红石更新 +const auto RedStoneUpdateCallback = + [](BlockSource& bs, BlockPos const& pos, int /* level */, bool /* isActive */) -> bool { + if (bs.getDimensionId() != getPlotDimensionId()) return true; + + debugger("[RedstoneUpdate] pos: " << pos.toString()); + + auto pps = PPos(pos); + if (!pps.isValid()) return true; + + auto meta = PlotDBStorage::getInstance().getPlot(pps.getPlotID()); + if (meta) { + if (meta->getPermissionTableConst().allowRedstoneUpdate) return true; + } + + return false; +}; +#define RedstoneUpdateHookMacro(NAME, TYPE, SYMBOL) \ + LL_TYPE_INSTANCE_HOOK( \ + NAME, \ + HookPriority::Normal, \ + TYPE, \ + SYMBOL, \ + void, \ + BlockSource& region, \ + BlockPos const& pos, \ + int strength, \ + bool isFirstTime \ + ) { \ + try { \ + if (RedStoneUpdateCallback(region, pos, strength, isFirstTime)) { \ + origin(region, pos, strength, isFirstTime); \ + } \ + } \ + CATCH; \ + } + +RedstoneUpdateHookMacro( + RedstoneUpdateHook1, + RedStoneWireBlock, + "?onRedstoneUpdate@RedStoneWireBlock@@UEBAXAEAVBlockSource@@AEBVBlockPos@@H_N@Z" +); +RedstoneUpdateHookMacro( + RedstoneUpdateHook2, + DiodeBlock, + "?onRedstoneUpdate@DiodeBlock@@UEBAXAEAVBlockSource@@AEBVBlockPos@@H_N@Z" +); +RedstoneUpdateHookMacro( + RedstoneUpdateHook3, + RedstoneTorchBlock, + "?onRedstoneUpdate@RedstoneTorchBlock@@UEBAXAEAVBlockSource@@AEBVBlockPos@@H_N@Z" +); +RedstoneUpdateHookMacro( + RedstoneUpdateHook4, + ComparatorBlock, + "?onRedstoneUpdate@ComparatorBlock@@UEBAXAEAVBlockSource@@AEBVBlockPos@@H_N@Z" +); + + +// 方块/实体爆炸 +const auto ExplodeCallback = [](Actor* /* source */, + BlockSource& bs, + Vec3 const& pos, + float explosionRadius, + bool /* fire */, + bool /* breaksBlocks */, + float /* maxResistance */) -> bool { + if (bs.getDimensionId() != getPlotDimensionId()) return true; + + debugger("[Explode] pos: " << pos.toString()); + + Radius r(pos, explosionRadius + 1); + auto land = r.getRangedPlots(); + auto& db = PlotDBStorage::getInstance(); + + for (auto& p : land) { + if (p.isRadiusOnBorder(r)) { + return false; // 禁止破坏边框 + } + + auto meta = db.getPlot(p.getPlotID()); + if (meta) { + if (!meta->getPermissionTableConst().allowExplode) { + return false; // 地皮禁止爆炸 + } + } + } + + return false; // 地皮世界禁止爆炸 +}; +LL_TYPE_INSTANCE_HOOK( + ExplodeHook, + HookPriority::Normal, + Level, + &Level::explode, + bool, + BlockSource& region, + Actor* source, + Vec3 const& pos, + float explosionRadius, + bool fire, + bool breaksBlocks, + float maxResistance, + bool allowUnderwater +) { + try { + if (ExplodeCallback(source, region, pos, explosionRadius, fire, breaksBlocks, maxResistance)) { + return origin(region, source, pos, explosionRadius, fire, breaksBlocks, maxResistance, allowUnderwater); + } + return false; + } + CATCH_RET(false); +} + + +// Hook Manager +auto GetHooks() { + return ll::memory::HookRegistrar< + PressurePlateTriggerHook, // 生物踩踏压力板 + ActorRideHook, // 生物骑乘 + WitherDestroyHook, // 凋零破坏方块 + PistonPushHook, // 活塞推动方块 + ExplodeHook, // 方块/实体 爆炸 + MobHurtEffectHook, // 生物受伤 + FarmDecayHook, // 耕地退化 + // 玩家操作物品展示框 + PlayerUseFrameHook1, + PlayerUseFrameHook2, + // 弹射物生成 + ProjectileSpawnHook1, + ProjectileSpawnHook2, + ProjectileSpawnHook3, + // 红石更新 + RedstoneUpdateHook1, + RedstoneUpdateHook2, + RedstoneUpdateHook3, + RedstoneUpdateHook4>(); +} +void registerHook() { GetHooks().hook(); } +void unregisterHook() { GetHooks().unhook(); } + + +} // namespace plo::event diff --git a/src/plotcraft/event/EventHook.h b/src/plotcraft/event/EventHook.h new file mode 100644 index 0000000..a95d6be --- /dev/null +++ b/src/plotcraft/event/EventHook.h @@ -0,0 +1,8 @@ +namespace plo::event { + +void registerHook(); + +void unregisterHook(); + + +} // namespace plo::event \ No newline at end of file diff --git a/src/plotcraft/event/PlotEvents.cc b/src/plotcraft/event/PlotEvents.cc index 4643432..a5aa612 100644 --- a/src/plotcraft/event/PlotEvents.cc +++ b/src/plotcraft/event/PlotEvents.cc @@ -3,13 +3,13 @@ namespace plo::event { // 玩家进入地皮 -Player* PlayerEnterPlot::getPlayer() const { return mPlayer; } -PlotPos const& PlayerEnterPlot::getPos() const { return mPos; } +Player* PlayerEnterPlot::getPlayer() const { return mPlayer; } +PPos const& PlayerEnterPlot::getPos() const { return mPos; } // 玩家离开地皮 -Player* PlayerLeavePlot::getPlayer() const { return mPlayer; } -PlotPos const& PlayerLeavePlot::getPos() const { return mPos; } +Player* PlayerLeavePlot::getPlayer() const { return mPlayer; } +PPos const& PlayerLeavePlot::getPos() const { return mPos; } // 玩家评论地皮之前 diff --git a/src/plotcraft/event/RuntimeMap.cc b/src/plotcraft/event/RuntimeMap.cc new file mode 100644 index 0000000..1bfe2fc --- /dev/null +++ b/src/plotcraft/event/RuntimeMap.cc @@ -0,0 +1,284 @@ +#include "RuntimeMap.h" +#include + +namespace plo::event { + + +// Last updated: 2024.8.21 & v1.21.3 +std::unordered_map> RuntimeMap::mData = { + {MapType::UseItemOn, + { + "minecraft:bed", // 床 + "minecraft:chest", // 箱子 + "minecraft:trapped_chest", // 陷阱箱 + "minecraft:crafting_table", // 制作台 + "minecraft:campfire", // 营火 + "minecraft:soul_campfire", // 灵魂营火 + "minecraft:composter", // 垃圾箱 + "minecraft:noteblock", // 音符盒 + "minecraft:jukebox", // 唱片机 + "minecraft:bell", // 钟 + "minecraft:daylight_detector", // 阳光探测器 + "minecraft:daylight_detector_inverted", // 阳光探测器(夜晚) + "minecraft:lectern", // 讲台 + "minecraft:cauldron", // 炼药锅 + "minecraft:lever", // 拉杆 + "minecraft:dragon_egg", // 龙蛋 + "minecraft:flower_pot", // 花盆 + "minecraft:respawn_anchor", // 重生锚 + "minecraft:glow_ink_sac", // 荧光墨囊 + "minecraft:end_crystal", // 末地水晶 + "minecraft:ender_eye", // 末影之眼 + "minecraft:flint_and_steel", // 打火石 + "minecraft:skull", // 头颅 + "minecraft:banner", // 旗帜 + "minecraft:bone_meal", // 骨粉 + + "minecraft:axolotl_bucket", // 美西螈桶 + "minecraft:powder_snow_bucket", // 细雪桶 + "minecraft:pufferfish_bucket", // 河豚桶 + "minecraft:tropical_fish_bucket", // 热带鱼桶 + "minecraft:salmon_bucket", // 桶装鲑鱼 + "minecraft:cod_bucket", // 鳕鱼桶 + "minecraft:water_bucket", // 水桶 + "minecraft:cod_bucket", // 鳕鱼桶 + "minecraft:lava_bucket", // 熔岩桶 + "minecraft:bucket", // 桶 + + "minecraft:shulker_box", // 潜影盒 + "minecraft:undyed_shulker_box", // 未染色的潜影盒 + "minecraft:white_shulker_box", // 白色潜影盒 + "minecraft:orange_shulker_box", // 橙色潜影盒 + "minecraft:magenta_shulker_box", // 品红色潜影盒 + "minecraft:light_blue_shulker_box", // 浅蓝色潜影盒 + "minecraft:yellow_shulker_box", // 黄色潜影盒 + "minecraft:lime_shulker_box", // 黄绿色潜影盒 + "minecraft:pink_shulker_box", // 粉红色潜影盒 + "minecraft:gray_shulker_box", // 灰色潜影盒 + "minecraft:light_gray_shulker_box", // 浅灰色潜影盒 + "minecraft:cyan_shulker_box", // 青色潜影盒 + "minecraft:purple_shulker_box", // 紫色潜影盒 + "minecraft:blue_shulker_box", // 蓝色潜影盒 + "minecraft:brown_shulker_box", // 棕色潜影盒 + "minecraft:green_shulker_box", // 绿色潜影盒 + "minecraft:red_shulker_box", // 红色潜影盒 + "minecraft:black_shulker_box", // 黑色潜影盒 + + "minecraft:stone_button", // 石头按钮 + "minecraft:wooden_button", // 木头按钮 + "minecraft:spruce_button", // 云杉木按钮 + "minecraft:birch_button", // 白桦木按钮 + "minecraft:jungle_button", // 丛林木按钮 + "minecraft:acacia_button", // 金合欢木按钮 + "minecraft:dark_oak_button", // 深色橡木按钮 + "minecraft:crimson_button", // 绯红木按钮 + "minecraft:warped_button", // 诡异木按钮 + "minecraft:polished_blackstone_button", // 磨制黑石按钮 + "minecraft:mangrove_button", // 红树木按钮 + "minecraft:cherry_button", // 樱花木按钮 + "minecraft:bamboo_button", // 竹按钮 + + "minecraft:trapdoor", // 活板门 + "minecraft:spruce_trapdoor", // 云杉木活板门 + "minecraft:birch_trapdoor", // 白桦木活板门 + "minecraft:jungle_trapdoor", // 丛林木活板门 + "minecraft:acacia_trapdoor", // 金合欢木活板门 + "minecraft:dark_oak_trapdoor", // 深色橡木活板门 + "minecraft:crimson_trapdoor", // 绯红木活板门 + "minecraft:warped_trapdoor", // 诡异木活板门 + "minecraft:copper_trapdoor", // 铜活板门 + "minecraft:exposed_copper_trapdoor", // 斑驳的铜活板门 + "minecraft:weathered_copper_trapdoor", // 锈蚀的铜活板门 + "minecraft:oxidized_copper_trapdoor", // 氧化的铜活板门 + "minecraft:waxed_copper_trapdoor", // 涂蜡的铜活板门 + "minecraft:waxed_exposed_copper_trapdoor", // 涂蜡的斑驳的铜活板门 + "minecraft:waxed_weathered_copper_trapdoor", // 涂蜡的锈蚀的铜活板门 + "minecraft:waxed_oxidized_copper_trapdoor", // 涂蜡的氧化的铜活板门 + "minecraft:mangrove_trapdoor", // 红树木活板门 + "minecraft:cherry_trapdoor", // 樱树木活板门 + "minecraft:bamboo_trapdoor", // 竹活板门 + + "minecraft:fence_gate", // 栅栏门 + "minecraft:spruce_fence_gate", // 云杉木栅栏门 + "minecraft:birch_fence_gate", // 白桦木栅栏门 + "minecraft:jungle_fence_gate", // 丛林木栅栏门 + "minecraft:acacia_fence_gate", // 金合欢木栅栏门 + "minecraft:dark_oak_fence_gate", // 深色橡木栅栏门 + "minecraft:crimson_fence_gate", // 绯红木栅栏门 + "minecraft:warped_fence_gate", // 诡异木栅栏门 + "minecraft:mangrove_fence_gate", // 红树木栅栏门 + "minecraft:cherry_fence_gate", // 樱树木栅栏门 + "minecraft:bamboo_fence_gate", // 竹栅栏门 + + "minecraft:wooden_door", // 橡木门 + "minecraft:spruce_door", // 云杉木门 + "minecraft:birch_door", // 白桦木门 + "minecraft:jungle_door", // 丛林木门 + "minecraft:acacia_door", // 金合欢木门 + "minecraft:dark_oak_door", // 深色橡木门 + "minecraft:crimson_door", // 绯红木门 + "minecraft:warped_door", // 诡异木门 + "minecraft:mangrove_door", // 红树木门 + "minecraft:cherry_door", // 樱树木门 + "minecraft:bamboo_door", // 竹门 + + "minecraft:wooden_axe", // 木斧 + "minecraft:stone_axe", // 石斧 + "minecraft:iron_axe", // 铁斧 + "minecraft:golden_axe", // 金斧 + "minecraft:diamond_axe", // 钻石斧 + "minecraft:netherite_axe", // 下界合金斧 + "minecraft:wooden_hoe", // 木锄 + "minecraft:stone_hoe", // 石锄 + "minecraft:iron_hoe", // 铁锄 + "minecraft:diamond_hoe", // 钻石锄 + "minecraft:golden_hoe", // 金锄 + "minecraft:netherite_hoe", // 下界合金锄 + "minecraft:wooden_shovel", // 木铲 + "minecraft:stone_shovel", // 石铲 + "minecraft:iron_shovel", // 铁铲 + "minecraft:diamond_shovel", // 钻石铲 + "minecraft:golden_shovel", // 金铲 + "minecraft:netherite_shovel", // 下界合金铲 + + "minecraft:standing_sign", // 站立的告示牌 + "minecraft:spruce_standing_sign", // 站立的云杉木告示牌 + "minecraft:birch_standing_sign", // 站立的白桦木告示牌 + "minecraft:jungle_standing_sign", // 站立的丛林木告示牌 + "minecraft:acacia_standing_sign", // 站立的金合欢木告示牌 + "minecraft:darkoak_standing_sign", // 站立的深色橡木告示牌 + "minecraft:mangrove_standing_sign", // 站立的红树木告示牌 + "minecraft:cherry_standing_sign", // 站立的樱树木告示牌 + "minecraft:bamboo_standing_sign", // 站立的竹子告示牌 + "minecraft:crimson_standing_sign", // 站立的绯红木告示牌 + "minecraft:warped_standing_sign", // 站立的诡异木告示牌 + "minecraft:wall_sign", // 墙上的告示牌 + "minecraft:spruce_wall_sign", // 墙上的云杉木告示牌 + "minecraft:birch_wall_sign", // 墙上的白桦木告示牌 + "minecraft:jungle_wall_sign", // 墙上的丛林木告示牌 + "minecraft:acacia_wall_sign", // 墙上的金合欢木告示牌 + "minecraft:darkoak_wall_sign", // 墙上的深色橡木告示牌 + "minecraft:mangrove_wall_sign", // 墙上的红树木告示牌 + "minecraft:cherry_wall_sign", // 墙上的樱树木告示牌 + "minecraft:bamboo_wall_sign", // 墙上的竹子告示牌 + "minecraft:crimson_wall_sign", // 墙上的绯红木告示牌 + "minecraft:warped_wall_sign" // 墙上的诡异木告示牌 + }}, + {MapType::InteractBlock, + { + "minecraft:cartography_table", // 制图台 + "minecraft:smithing_table", // 锻造台 + "minecraft:furnace", // 熔炉 + "minecraft:blast_furnace", // 高炉 + "minecraft:smoker", // 烟熏炉 + "minecraft:brewing_stand", // 酿造台 + "minecraft:anvil", // 铁砧 + "minecraft:grindstone", // 砂轮 + "minecraft:enchanting_table", // 附魔台 + "minecraft:barrel", // 木桶 + "minecraft:beacon", // 信标 + "minecraft:hopper", // 漏斗 + "minecraft:dropper", // 投掷器 + "minecraft:dispenser", // 发射器 + "minecraft:loom", // 织布机 + "minecraft:stonecutter_block", // 切石机 + "minecraft:lit_furnace", // 燃烧中的熔炉 + "minecraft:lit_blast_furnace", // 燃烧中的高炉 + "minecraft:lit_smoker" // 燃烧中的烟熏炉 + }}, + {MapType::AnimalEntity, + { + "minecraft:axolotl", // 美西螈 + "minecraft:bat", // 蝙蝠 + "minecraft:cat", // 猫 + "minecraft:chicken", // 鸡 + "minecraft:cod", // 鳕鱼 + "minecraft:cow", // 牛 + "minecraft:donkey", // 驴 + "minecraft:fox", // 狐狸 + "minecraft:glow_squid", // 发光鱿鱼 + "minecraft:horse", // 马 + "minecraft:mooshroom", // 蘑菇牛 + "minecraft:mule", // 驴 + "minecraft:ocelot", // 豹猫 + "minecraft:parrot", // 鹦鹉 + "minecraft:pig", // 猪 + "minecraft:rabbit", // 兔子 + "minecraft:salmon", // 鲑鱼 + "minecraft:snow_golem", // 雪傀儡 + "minecraft:sheep", // 羊 + "minecraft:skeleton_horse", // 骷髅马 + "minecraft:squid", // 鱿鱼 + "minecraft:strider", // 炽足兽 + "minecraft:tropical_fish", // 热带鱼 + "minecraft:turtle", // 海龟 + "minecraft:villager_v2", // 村民 + "minecraft:wandering_trader", // 流浪商人 + "minecraft:npc" // NPC + }}, + {MapType::MobEntity, + { + // type A + "minecraft:pufferfish", // 河豚 + "minecraft:bee", // 蜜蜂 + "minecraft:dolphin", // 海豚 + "minecraft:goat", // 山羊 + "minecraft:iron_golem", // 铁傀儡 + "minecraft:llama", // 羊驼 + "minecraft:llama_spit", // 羊驼唾沫 + "minecraft:wolf", // 狼 + "minecraft:panda", // 熊猫 + "minecraft:polar_bear", // 北极熊 + "minecraft:enderman", // 末影人 + "minecraft:piglin", // 猪灵 + "minecraft:spider", // 蜘蛛 + "minecraft:cave_spider", // 洞穴蜘蛛 + "minecraft:zombie_pigman", // 僵尸猪人 + + // type B + "minecraft:blaze", // 烈焰人 + "minecraft:small_fireball", // 小火球 + "minecraft:creeper", // 爬行者 + "minecraft:drowned", // 溺尸 + "minecraft:elder_guardian", // 远古守卫者 + "minecraft:endermite", // 末影螨 + "minecraft:evocation_illager", // 唤魔者 + "minecraft:evocation_fang", // 唤魔者尖牙 + "minecraft:ghast", // 恶魂 + "minecraft:fireball", // 火球 + "minecraft:guardian", // 守卫者 + "minecraft:hoglin", // 疣猪兽 + "minecraft:husk", // 凋零骷髅 + "minecraft:magma_cube", // 岩浆怪 + "minecraft:phantom", // 幻翼 + "minecraft:pillager", // 掠夺者 + "minecraft:ravager", // 劫掠兽 + "minecraft:shulker", // 潜影贝 + "minecraft:shulker_bullet", // 潜影贝弹射物 + "minecraft:silverfish", // 蠹虫 + "minecraft:skeleton", // 骷髅 + "minecraft:skeleton_horse", // 骷髅马 + "minecraft:slime", // 史莱姆 + "minecraft:vex", // 恶魂兽 + "minecraft:vindicator", // 卫道士 + "minecraft:witch", // 女巫 + "minecraft:wither_skeleton", // 凋零骷髅 + "minecraft:zoglin", // 疣猪兽 + "minecraft:zombie", // 僵尸 + "minecraft:zombie_villager_v2", // 僵尸村民 + "minecraft:piglin_brute", // 猪灵蛮兵 + "minecraft:ender_dragon", // 末影龙 + "minecraft:dragon_fireball", // 末影龙火球 + "minecraft:wither", // 凋零 + "minecraft:wither_skull", // 凋零之首 + "minecraft:wither_skull_dangerous" // 蓝色凋灵之首(Wiki) + }} +}; + +bool RuntimeMap::has(MapType type, std::string const& typeName) { + auto& v = mData[type]; + return std::find(v.begin(), v.end(), typeName) != v.end(); +} + + +} // namespace plo::event \ No newline at end of file diff --git a/src/plotcraft/event/RuntimeMap.h b/src/plotcraft/event/RuntimeMap.h new file mode 100644 index 0000000..7a0409c --- /dev/null +++ b/src/plotcraft/event/RuntimeMap.h @@ -0,0 +1,30 @@ +#pragma once +#include +#include + +namespace plo::event { + +enum class MapType : int { + UseItemOn = 0, // 使用物品 + InteractBlock = 1, // 方块交互 + AnimalEntity = 2, // 动物实体 + MobEntity = 3 // 生物实体 +}; + +// 运行时表 +// 用于大量if时预检查字符串是否存在 +struct RuntimeMap { +public: + RuntimeMap() = delete; + RuntimeMap(RuntimeMap&&) = delete; + RuntimeMap(const RuntimeMap&) = delete; + RuntimeMap& operator=(RuntimeMap&&) = delete; + RuntimeMap& operator=(const RuntimeMap&) = delete; + + static std::unordered_map> mData; + + static bool has(MapType type, std::string const& typeName); +}; + + +} // namespace plo::event \ No newline at end of file diff --git a/src/plotcraft/event/Scheduler.cc b/src/plotcraft/event/Scheduler.cc new file mode 100644 index 0000000..9bd496e --- /dev/null +++ b/src/plotcraft/event/Scheduler.cc @@ -0,0 +1,145 @@ +#include "Scheduler.h" +#include "ll/api/chrono/GameChrono.h" +#include "ll/api/event/EventBus.h" +#include "ll/api/event/ListenerBase.h" +#include "ll/api/schedule/Scheduler.h" +#include "ll/api/schedule/Task.h" +#include "ll/api/service/Bedrock.h" +#include "mc/common/wrapper/optional_ref.h" +#include "mc/network/packet/TextPacket.h" +#include "mc/world/actor/player/Player.h" +#include "mc/world/gamemode/GameMode.h" +#include "mc/world/level/Level.h" +#include "plotcraft/Config.h" +#include "plotcraft/core/PPos.h" +#include "plotcraft/core/Utils.h" +#include "plotcraft/data/PlayerNameDB.h" +#include "plotcraft/data/PlotDBStorage.h" +#include "plotcraft/data/PlotMetadata.h" +#include "plotcraft/event/PlotEvents.h" +#include "plotcraft/utils/Mc.h" +#include "plotcraft/utils/Utils.h" +#include "plugin/MyPlugin.h" +#include +#include + + +#include "plotcraft/utils/Debugger.h" + + +ll::schedule::GameTickScheduler mTickScheduler; // Tick调度 + +using string = std::string; +using ll::chrono_literals::operator""_tick; + + +class CustomEventHelper { +public: + // player PPos Dimension + std::unordered_map> mPlayerPos; // 玩家位置缓存 + + bool has(string const& uid) { return mPlayerPos.find(uid) != mPlayerPos.end(); } + + void set(string uid, plo::PPos pps, int dim) { mPlayerPos[uid] = std::make_pair(pps, dim); } + + std::pair const& get(string const& uid) { + auto it = mPlayerPos.find(uid); + if (it == mPlayerPos.end()) { + mPlayerPos[uid] = std::make_pair(plo::PPos{}, -1); + return mPlayerPos[uid]; + } + return it->second; + } +} helper; + + +namespace plo::event { +using namespace core; + + +void buildTipMessage(Player& p, PPos const& pps, PlayerNameDB* ndb, PlotDBStorage* pdb) { + try { + PlotMetadataPtr plot = pdb->getPlot(pps.getPlotID()); + if (plot == nullptr) plot = PlotMetadata::make(pps.getPlotID(), pps.mX, pps.mZ); + + TextPacket pkt = TextPacket(); + pkt.mType = TextPacketType::Tip; + if (pps.isValid()) { + auto owner = plot->getPlotOwner(); + // clang-format off + pkt.mMessage = fmt::format( + "地皮: {0}\n主人: {1} | 名称: {2}\n出售: {3} | 价格: {4}{5}", + pps.toString(), + owner.empty() ? "无主" : ndb->getPlayerName(owner), + plot->getPlotName(), + owner.empty() ? "§a✔§r" : plot->isSale() ? "§a✔§r" : "§c✘§r", + owner.empty() ? config::cfg.plotWorld.buyPlotPrice : plot->isSale() ? plot->getSalePrice() : 0, + owner.empty() ? "\n输入:/plo buy 打开购买菜单" : "" + ); + // clang-format on + } else pkt.mMessage = fmt::format("{0} | 地皮世界\n输入: /plo 打开地皮菜单", PLUGIN_TITLE); // Tip3 + + p.sendNetworkPacket(pkt); + } catch (std::exception const& e) { + my_plugin::MyPlugin::getInstance().getSelf().getLogger().error( + "Fail in {}\nstd::exception: {}", + __FUNCTION__, + e.what() + ); + } catch (...) { + my_plugin::MyPlugin::getInstance().getSelf().getLogger().error("Fail in {}\nunknown exception", __FUNCTION__); + } +} + + +void initPlotEventScheduler() { + auto* bus = &ll::event::EventBus::getInstance(); + auto* pdb = &data::PlotDBStorage::getInstance(); + auto* ndb = &data::PlayerNameDB::getInstance(); + + mTickScheduler.add(4_tick, [bus, pdb, ndb]() { + auto lv = ll::service::getLevel(); + if (!lv) return; // nullptr + + lv->forEachPlayer([bus, pdb, ndb](Player& p) { + if (p.isSimulatedPlayer() || p.isLoading()) return true; // skip simulated player + + int const playerDimid = p.getDimensionId(); + int const plotDimid = getPlotDimensionId(); + auto const name = p.getRealName(); + auto const& pair = helper.get(name); + PPos const pps{p.getPosition()}; + + if (playerDimid != plotDimid) { + // 玩家通过传送离开地皮维度 + // 地皮维度 => 其它维度 触发 + // 其它维度 => 地皮维度 忽略 + // 其它维度 => 其它维度 忽略 + if (pair.second != -1 && pair.second != playerDimid && pair.second == plotDimid) { + debugger("离开地皮(维度): " << pps.toString()); + bus->publish(PlayerLeavePlot(pair.first, &p)); + helper.set(name, pps, playerDimid); + } + return true; + } + + if (pdb->getPlayerSetting(p.getUuid().asString()).showPlotTip) buildTipMessage(p, pps, ndb, pdb); + bool const valid = pps.isValid(); + + // Leave + if ((!valid && pair.first != pps) || (valid && pair.first != pps)) { + debugger("离开地皮(移动): " << pair.first.toString()); + bus->publish(PlayerLeavePlot(pair.first, &p)); // use last position + } + // Join + if (valid && pair.first != pps) { + debugger("进入地皮: " << pps.toString()); + bus->publish(PlayerEnterPlot(pps, &p)); + } + helper.set(name, pps, playerDimid); + return true; + }); + }); +} + +} // namespace plo::event \ No newline at end of file diff --git a/src/plotcraft/event/Scheduler.h b/src/plotcraft/event/Scheduler.h new file mode 100644 index 0000000..79fa0b2 --- /dev/null +++ b/src/plotcraft/event/Scheduler.h @@ -0,0 +1,6 @@ + +namespace plo::event { + +void initPlotEventScheduler(); + +} \ No newline at end of file diff --git a/src/plotcraft/event/hook/ArmorStandSwapItemEvent.cc b/src/plotcraft/event/hook/ArmorStandSwapItemEvent.cc new file mode 100644 index 0000000..3e6679a --- /dev/null +++ b/src/plotcraft/event/hook/ArmorStandSwapItemEvent.cc @@ -0,0 +1,47 @@ +#include "ArmorStandSwapItemEvent.h" +#include "ll/api/event/Emitter.h" +#include "ll/api/event/EmitterBase.h" +#include "ll/api/event/EventBus.h" +#include "ll/api/memory/Hook.h" +#include +#include + + +namespace more_events { + +Puv::Legacy::EquipmentSlot ArmorStandSwapItemEvent::getSlot() const { return mSlot; } +Player& ArmorStandSwapItemEvent::getPlayer() const { return mPlayer; } +Mob& ArmorStandSwapItemEvent::getArmorStand() const { return mArmorStand; }; + + +LL_TYPE_INSTANCE_HOOK( + ArmorStandSwapItemHook, + HookPriority::Normal, + ArmorStand, + &ArmorStand::_trySwapItem, + bool, + Player& player, + Puv::Legacy::EquipmentSlot slot +) { + auto before = ArmorStandSwapItemEvent(slot, player, *this); + ll::event::EventBus::getInstance().publish(before); + + if (before.isCancelled()) { + return false; + } + + return origin(player, slot); +} + + +static std::unique_ptr emitterFactory1(ll::event::ListenerBase&); +class ArmorStandSwapItemEventEmitter : public ll::event::Emitter { + ll::memory::HookRegistrar hook; +}; + +static std::unique_ptr emitterFactory1(ll::event::ListenerBase&) { + return std::make_unique(); +} + + +} // namespace more_events \ No newline at end of file diff --git a/src/plotcraft/event/hook/ArmorStandSwapItemEvent.h b/src/plotcraft/event/hook/ArmorStandSwapItemEvent.h new file mode 100644 index 0000000..90fdf43 --- /dev/null +++ b/src/plotcraft/event/hook/ArmorStandSwapItemEvent.h @@ -0,0 +1,30 @@ +#pragma once +#include "ll/api/event/Cancellable.h" +#include "ll/api/event/Event.h" +#include "mc/world/actor/Mob.h" +#include "mc/world/actor/player/Player.h" + + +namespace more_events { + + +class ArmorStandSwapItemEvent final : public ll::event::Cancellable { +protected: + Puv::Legacy::EquipmentSlot mSlot; + Player& mPlayer; + Mob& mArmorStand; + +public: + constexpr explicit ArmorStandSwapItemEvent(Puv::Legacy::EquipmentSlot slot, Player& player, Mob& armorStand) + : Cancellable(), + mSlot(slot), + mPlayer(player), + mArmorStand(armorStand) {} + + Puv::Legacy::EquipmentSlot getSlot() const; + Player& getPlayer() const; + Mob& getArmorStand() const; +}; + + +} // namespace more_events \ No newline at end of file diff --git a/src/plotcraft/event/hook/PlayerAttackBlockEvent.cc b/src/plotcraft/event/hook/PlayerAttackBlockEvent.cc new file mode 100644 index 0000000..643568e --- /dev/null +++ b/src/plotcraft/event/hook/PlayerAttackBlockEvent.cc @@ -0,0 +1,45 @@ +#include "PlayerAttackBlockEvent.h" +#include "ll/api/event/Emitter.h" +#include "ll/api/event/EmitterBase.h" +#include "ll/api/event/EventBus.h" +#include "ll/api/memory/Hook.h" +#include + + +namespace more_events { + +BlockPos const& PlayerAttackBlockEvent::getPos() const { return mPos; } +optional_ref PlayerAttackBlockEvent::getPlayer() const { return mPlayer; } + + +LL_TYPE_INSTANCE_HOOK( + PlayerAttackBlockEventHook, + HookPriority::Normal, + Block, + &Block::attack, + bool, + Player* player, + BlockPos const& pos +) { + auto before = PlayerAttackBlockEvent(pos, player); + ll::event::EventBus::getInstance().publish(before); + + if (before.isCancelled()) { + return false; + } + + return origin(player, pos); +} + + +static std::unique_ptr emitterFactory1(ll::event::ListenerBase&); +class PlayerAttackBlockEventEmitter : public ll::event::Emitter { + ll::memory::HookRegistrar hook; +}; + +static std::unique_ptr emitterFactory1(ll::event::ListenerBase&) { + return std::make_unique(); +} + + +} // namespace more_events \ No newline at end of file diff --git a/src/plotcraft/event/hook/PlayerAttackBlockEvent.h b/src/plotcraft/event/hook/PlayerAttackBlockEvent.h new file mode 100644 index 0000000..be5f413 --- /dev/null +++ b/src/plotcraft/event/hook/PlayerAttackBlockEvent.h @@ -0,0 +1,28 @@ +#pragma once +#include "ll/api/event/Cancellable.h" +#include "ll/api/event/Event.h" +#include "mc/common/wrapper/optional_ref.h" +#include "mc/world/actor/player/Player.h" +#include "mc/world/level/BlockPos.h" + + +namespace more_events { + + +class PlayerAttackBlockEvent final : public ll::event::Cancellable { +protected: + BlockPos const& mPos; + optional_ref mPlayer; + +public: + constexpr explicit PlayerAttackBlockEvent(BlockPos const& pos, optional_ref player) + : Cancellable(), + mPos(pos), + mPlayer(player) {} + + BlockPos const& getPos() const; + optional_ref getPlayer() const; +}; + + +} // namespace more_events \ No newline at end of file diff --git a/src/plotcraft/event/hook/PlayerDropItemEvent.cc b/src/plotcraft/event/hook/PlayerDropItemEvent.cc new file mode 100644 index 0000000..21f3758 --- /dev/null +++ b/src/plotcraft/event/hook/PlayerDropItemEvent.cc @@ -0,0 +1,65 @@ +#include "PlayerDropItemEvent.h" +#include "ll/api/event/Emitter.h" +#include "ll/api/event/EmitterBase.h" +#include "ll/api/event/EventBus.h" +#include "ll/api/memory/Hook.h" +#include + + +namespace more_events { + + +Player& PlayerDropItemEvent::getPlayer() const { return mPlayer; }; +ItemStack const& PlayerDropItemEvent::getItemStack() const { return mItemStack; }; + + +LL_TYPE_INSTANCE_HOOK( + PlayerDropItemHook1, + HookPriority::Normal, + Player, + "?drop@Player@@UEAA_NAEBVItemStack@@_N@Z", + bool, + ItemStack const& item, + bool randomly +) { + auto ev = PlayerDropItemEvent(*this, item); + ll::event::EventBus::getInstance().publish(ev); + if (ev.isCancelled()) return false; + + return origin(item, randomly); +} + +LL_TYPE_INSTANCE_HOOK( + PlayerDropItemHook2, + HookPriority::Normal, + ComplexInventoryTransaction, + "?handle@ComplexInventoryTransaction@@UEBA?AW4InventoryTransactionError@@AEAVPlayer@@_N@Z", + InventoryTransactionError, + Player& player, + bool isSenderAuthority +) { + if (type == ComplexInventoryTransaction::Type::NormalTransaction) { + InventorySource source(InventorySourceType::ContainerInventory, ContainerID::Inventory); + auto& actions = data.getActions(source); + if (actions.size() == 1) { + auto ev = + PlayerDropItemEvent(player, const_cast(player.getInventory().getItem(actions[0].mSlot))); + ll::event::EventBus::getInstance().publish(ev); + if (ev.isCancelled()) return InventoryTransactionError::NoError; + } + } + return origin(player, isSenderAuthority); +} + + +static std::unique_ptr emitterFactory(ll::event::ListenerBase&); +class PlayerDropItemEventEmitter : public ll::event::Emitter { + ll::memory::HookRegistrar hook; +}; + +static std::unique_ptr emitterFactory(ll::event::ListenerBase&) { + return std::make_unique(); +} + + +} // namespace more_events diff --git a/src/plotcraft/event/hook/PlayerDropItemEvent.h b/src/plotcraft/event/hook/PlayerDropItemEvent.h new file mode 100644 index 0000000..5224348 --- /dev/null +++ b/src/plotcraft/event/hook/PlayerDropItemEvent.h @@ -0,0 +1,26 @@ +#pragma once +#include "ll/api/event/Cancellable.h" +#include "ll/api/event/Event.h" +#include "mc/world/actor/player/Player.h" +#include "mc/world/item/registry/ItemStack.h" + +namespace more_events { + + +class PlayerDropItemEvent final : public ll::event::Cancellable { +protected: + Player& mPlayer; + ItemStack const& mItemStack; + +public: + constexpr explicit PlayerDropItemEvent(Player& player, ItemStack const& itemStack) + : Cancellable(), + mPlayer(player), + mItemStack(itemStack) {} + + Player& getPlayer() const; + ItemStack const& getItemStack() const; +}; + + +} // namespace more_events \ No newline at end of file diff --git a/src/plotcraft/event/hook/SculkBlockGrowthEvent.cc b/src/plotcraft/event/hook/SculkBlockGrowthEvent.cc index bc122d7..6255e83 100644 --- a/src/plotcraft/event/hook/SculkBlockGrowthEvent.cc +++ b/src/plotcraft/event/hook/SculkBlockGrowthEvent.cc @@ -9,11 +9,11 @@ #include "mc/world/level/block/utils/SculkBlockBehavior.h" -namespace plo::event::hook { +namespace more_events { -BlockSource* SculkBlockGrowthEvent::getSource() const { return mSource; } -BlockPos const& SculkBlockGrowthEvent::getPos() const { return mPos; } +optional_ref SculkBlockGrowthEvent::getBlockSource() const { return mBlockSource; } +BlockPos const& SculkBlockGrowthEvent::getPos() const { return mPos; } LL_STATIC_HOOK( @@ -22,30 +22,29 @@ LL_STATIC_HOOK( "?_placeGrowthAt@SculkBlockBehavior@@CAXAEAVIBlockWorldGenAPI@@PEAVBlockSource@@AEBVBlockPos@@AEAVRandom@@" "AEAVSculkSpreader@@@Z", void, - IBlockWorldGenAPI& a1, // target - BlockSource* a2, // region - BlockPos const& a3, // pos - Random& a4, // random + IBlockWorldGenAPI& target, + BlockSource* region, + BlockPos const& pos, + Random& random, SculkSpreader& a5 ) { - auto ev = SculkBlockGrowthEvent(a2, a3); + auto ev = SculkBlockGrowthEvent(region, pos); ll::event::EventBus::getInstance().publish(ev); if (ev.isCancelled()) { return; } - origin(a1, a2, a3, a4, a5); + origin(target, region, pos, random, a5); } -static std::unique_ptr emitterFactory(ll::event::ListenerBase&); -class SculkBlockGrowthEventEmitter : public ll::event::Emitter { +static std::unique_ptr emitterFactory1(ll::event::ListenerBase&); +class SculkBlockGrowthEventEmitter : public ll::event::Emitter { ll::memory::HookRegistrar hook; }; - -static std::unique_ptr emitterFactory(ll::event::ListenerBase&) { +static std::unique_ptr emitterFactory1(ll::event::ListenerBase&) { return std::make_unique(); } -} // namespace plo::event::hook \ No newline at end of file +} // namespace more_events \ No newline at end of file diff --git a/src/plotcraft/event/hook/SculkBlockGrowthEvent.h b/src/plotcraft/event/hook/SculkBlockGrowthEvent.h index 8a664d0..e07f587 100644 --- a/src/plotcraft/event/hook/SculkBlockGrowthEvent.h +++ b/src/plotcraft/event/hook/SculkBlockGrowthEvent.h @@ -1,26 +1,28 @@ +#pragma once #include "ll/api/event/Cancellable.h" #include "ll/api/event/Event.h" +#include "mc/common/wrapper/optional_ref.h" #include "mc/world/level/BlockPos.h" #include "mc/world/level/BlockSource.h" -namespace plo::event::hook { +namespace more_events { class SculkBlockGrowthEvent final : public ll::event::Cancellable { protected: - BlockSource* mSource; - BlockPos const& mPos; + optional_ref mBlockSource; + BlockPos const& mPos; public: - constexpr explicit SculkBlockGrowthEvent(BlockSource* source, BlockPos const& pos) + constexpr explicit SculkBlockGrowthEvent(optional_ref source, BlockPos const& pos) : Cancellable(), - mSource(source), + mBlockSource(source), mPos(pos) {} - BlockPos const& getPos() const; - BlockSource* getSource() const; + BlockPos const& getPos() const; + optional_ref getBlockSource() const; }; -} // namespace plo::event::hook \ No newline at end of file +} // namespace more_events \ No newline at end of file diff --git a/src/plotcraft/event/hook/SculkSpreadEvent.cc b/src/plotcraft/event/hook/SculkSpreadEvent.cc index b037c47..c94252e 100644 --- a/src/plotcraft/event/hook/SculkSpreadEvent.cc +++ b/src/plotcraft/event/hook/SculkSpreadEvent.cc @@ -7,7 +7,7 @@ #include "mc/world/level/block/utils/SculkVeinBlockBehavior.h" -namespace plo::event::hook { +namespace more_events { BlockPos const& SculkSpreadEvent::getPos() const { return mPos; } @@ -15,7 +15,7 @@ optional_ref SculkSpreadEvent::getBlockSource() const { return mBlo LL_STATIC_HOOK( - SculkVeinSpreadEventHook, + SculkSpreadEventHook, ll::memory::HookPriority::Normal, "?_attemptPlaceSculk@SculkVeinBlockBehavior@@CA_NAEAVIBlockWorldGenAPI@@PEAVBlockSource@@AEBVBlockPos@@" "AEAVSculkSpreader@@AEAVRandom@@@Z", @@ -36,14 +36,14 @@ LL_STATIC_HOOK( } -static std::unique_ptr emitterFactory(ll::event::ListenerBase&); -class SculkSpreadEventEmitter : public ll::event::Emitter { - ll::memory::HookRegistrar hook; +static std::unique_ptr emitterFactory1(ll::event::ListenerBase&); +class SculkSpreadEventEmitter : public ll::event::Emitter { + ll::memory::HookRegistrar hook; }; -static std::unique_ptr emitterFactory(ll::event::ListenerBase&) { +static std::unique_ptr emitterFactory1(ll::event::ListenerBase&) { return std::make_unique(); } -} // namespace plo::event::hook \ No newline at end of file +} // namespace more_events \ No newline at end of file diff --git a/src/plotcraft/event/hook/SculkSpreadEvent.h b/src/plotcraft/event/hook/SculkSpreadEvent.h index 8fb3e0a..f8af2be 100644 --- a/src/plotcraft/event/hook/SculkSpreadEvent.h +++ b/src/plotcraft/event/hook/SculkSpreadEvent.h @@ -1,3 +1,4 @@ +#pragma once #include "ll/api/event/Cancellable.h" #include "ll/api/event/Event.h" #include "mc/common/wrapper/optional_ref.h" @@ -5,7 +6,7 @@ #include "mc/world/level/BlockSource.h" -namespace plo::event::hook { +namespace more_events { class SculkSpreadEvent final : public ll::event::Cancellable { @@ -24,4 +25,4 @@ class SculkSpreadEvent final : public ll::event::Cancellable { }; -} // namespace plo::event::hook \ No newline at end of file +} // namespace more_events \ No newline at end of file diff --git a/src/plotcraft/gui/Global.h b/src/plotcraft/gui/Global.h index 83fc7f4..be9bc2f 100644 --- a/src/plotcraft/gui/Global.h +++ b/src/plotcraft/gui/Global.h @@ -11,18 +11,16 @@ #include "mc/world/level/dimension/VanillaDimensions.h" #include "plotcraft/Config.h" #include "plotcraft/EconomyQueue.h" -#include "plotcraft/PlotPos.h" -#include "plotcraft/core/CoreUtils.h" +#include "plotcraft/core/PPos.h" +#include "plotcraft/core/Utils.h" #include "plotcraft/data/PlayerNameDB.h" -#include "plotcraft/data/PlotBDStorage.h" +#include "plotcraft/data/PlotDBStorage.h" #include "plotcraft/data/PlotMetadata.h" #include "plotcraft/event/PlotEvents.h" #include "plotcraft/utils/Date.h" +#include "plotcraft/utils/EconomySystem.h" #include "plotcraft/utils/JsonHelper.h" #include "plotcraft/utils/Mc.h" -#include "plotcraft/utils/Menu.h" -#include "plotcraft/utils/Moneys.h" -#include "plotcraft/utils/Text.h" #include "plotcraft/utils/Utils.h" #include #include @@ -33,11 +31,11 @@ namespace plo::gui { - using namespace plo::data; using namespace ll::form; using namespace plo::utils; -using namespace plo::core_utils; +using namespace plo::core; +using namespace plo::mc; namespace pev = plo::event; diff --git a/src/plotcraft/gui/PlayerSettingGUI.cc b/src/plotcraft/gui/PlayerSettingGUI.cc index 6432f93..4ccc38f 100644 --- a/src/plotcraft/gui/PlayerSettingGUI.cc +++ b/src/plotcraft/gui/PlayerSettingGUI.cc @@ -6,7 +6,7 @@ namespace plo::gui { void PlayerSettingGUI(Player& player) { CustomForm fm{PLUGIN_TITLE}; - auto setting = data::PlotBDStorage::getInstance().getPlayerSetting(player.getUuid().asString()); + auto setting = data::PlotDBStorage::getInstance().getPlayerSetting(player.getUuid().asString()); auto i18n = ll::i18n::getInstance().get(); auto settingJson = JsonHelper::structToJson(setting); @@ -33,7 +33,7 @@ void PlayerSettingGUI(Player& player) { JsonHelper::jsonToStruct(setj, it); - data::PlotBDStorage::getInstance().setPlayerSetting(pl.getUuid().asString(), it); + data::PlotDBStorage::getInstance().setPlayerSetting(pl.getUuid().asString(), it); sendText(pl, "设置成功"); }); } diff --git a/src/plotcraft/gui/PlotCommentGUI.cc b/src/plotcraft/gui/PlotCommentGUI.cc index e5e66bc..cf38ea5 100644 --- a/src/plotcraft/gui/PlotCommentGUI.cc +++ b/src/plotcraft/gui/PlotCommentGUI.cc @@ -9,7 +9,7 @@ void PlotCommentGUI(Player& player, PlotMetadataPtr pt) { bool const hasOwner = !pt->getPlotOwner().empty(); // 是否有主人 if (!hasOwner) { - sendText(player, "你不能评论这个地皮,因为它没有主人。"); + sendText(player, "你不能评论这个地皮,因为它没有主人。"); return; } @@ -70,7 +70,7 @@ void _publishComment(Player& player, PlotMetadataPtr pt) { sendText(pl, "评论已发布"); } else { - sendText(pl, "评论发布失败"); + sendText(pl, "评论发布失败"); } }); } @@ -78,8 +78,9 @@ void _publishComment(Player& player, PlotMetadataPtr pt) { void _showCommentOperation(Player& player, PlotMetadataPtr pt, CommentID id) { auto const ct = *pt->getComment(id); - bool const isOwner = player.getUuid().asString() == pt->getPlotOwner(); + bool const isOwner = pt->isOwner(player.getUuid().asString()); bool const isCommentOwner = player.getUuid().asString() == ct.mCommentPlayer; + bool const isAdmin = PlotDBStorage::getInstance().isAdmin(player.getUuid().asString()); auto& ndb = PlayerNameDB::getInstance(); @@ -93,13 +94,13 @@ void _showCommentOperation(Player& player, PlotMetadataPtr pt, CommentID id) { ct.mContent )); - if (isCommentOwner) { + if (isCommentOwner || isAdmin) { fm.appendButton("编辑评论", "textures/ui/book_edit_default", "path", [pt, id](Player& pl) { _editComment(pl, pt, id); }); } - if (isCommentOwner || isOwner) { + if (isCommentOwner || isOwner || isAdmin) { fm.appendButton("删除评论", "textures/ui/icon_trash", "path", [pt, id](Player& pl) { pev::PlayerDeletePlotComment ev{&pl, pt, id}; ll::event::EventBus::getInstance().publish(ev); @@ -110,7 +111,7 @@ void _showCommentOperation(Player& player, PlotMetadataPtr pt, CommentID id) { if (ok) { sendText(pl, "评论已删除"); } else { - sendText(pl, "评论删除失败"); + sendText(pl, "评论删除失败"); } }); } @@ -153,7 +154,7 @@ void _editComment(Player& player, PlotMetadataPtr pt, CommentID id) { sendText(pl, "评论已修改"); } else { - sendText(pl, "评论修改失败"); + sendText(pl, "评论修改失败"); } }); } diff --git a/src/plotcraft/gui/PlotGUI.cc b/src/plotcraft/gui/PlotGUI.cc index b77447e..1539fe0 100644 --- a/src/plotcraft/gui/PlotGUI.cc +++ b/src/plotcraft/gui/PlotGUI.cc @@ -10,7 +10,7 @@ void _selectPlot(Player& player) { fm.appendButton("返回", "textures/ui/icon_import", "path", [](Player& pl) { MainGUI(pl); }); - auto plots = data::PlotBDStorage::getInstance().getPlots(); + auto plots = data::PlotDBStorage::getInstance().getPlots(); for (auto const& plt : plots) { if (!plt->isOwner(player.getUuid().asString())) continue; // 不是自己的地皮 @@ -33,6 +33,7 @@ void PlotGUI(Player& player, PlotMetadataPtr pt, bool ret) { bool const hasSale = pt->isSale(); // 是否出售 bool const isOwner = hasOwner && player.getUuid().asString() == pt->getPlotOwner(); // 是否是主人 bool const isSharedMember = pt->isSharedPlayer(player.getUuid().asString()); // 是否是地皮共享成员 + bool const isAdmin = PlotDBStorage::getInstance().isAdmin(player.getUuid().asString()); // 是否是管理员 fm.setContent(fmt::format( "地皮 {} 的元数据:\n地皮主人: {}\n地皮名称: {}\n是否出售: {}\n出售价格: {}\n ", @@ -48,14 +49,14 @@ void PlotGUI(Player& player, PlotMetadataPtr pt, bool ret) { if ((!hasOwner || hasSale) && !isOwner) fm.appendButton("购买地皮", "textures/ui/confirm", "path", [pt](Player& pl) { _buyPlot(pl, pt); }); - if ((isOwner || isSharedMember) && utils::some(cfg.allowedPlotTeleportDim, player.getDimensionId().id)) + if (((isOwner || isSharedMember) && utils::some(cfg.allowedPlotTeleportDim, player.getDimensionId().id)) || isAdmin) fm.appendButton("传送到此地皮", "textures/ui/move", "path", [pt](Player& pl) { - auto const v3 = PlotPos{pt->getX(), pt->getZ()}.getSafestPos(); + auto const v3 = PPos{pt->getX(), pt->getZ()}.getSafestPos(); pl.teleport(v3, getPlotDimensionId()); sendText(pl, "传送成功"); }); - if (isOwner) { + if (isOwner || isAdmin) { fm.appendButton("权限管理", "textures/ui/gear", "path", [pt](Player& pl) { PlotPermissionGUI(pl, pt); }); fm.appendButton("修改地皮名称", "textures/ui/book_edit_default", "path", [pt](Player& pl) { @@ -107,7 +108,7 @@ void _changePlotName(Player& player, PlotMetadataPtr pt) { sendText(pl, "地皮名称已修改"); } else { - sendText(pl, "地皮名称修改失败"); + sendText(pl, "地皮名称修改失败"); } }); } diff --git a/src/plotcraft/gui/PlotSaleGUI.cc b/src/plotcraft/gui/PlotSaleGUI.cc index e247d10..1b37436 100644 --- a/src/plotcraft/gui/PlotSaleGUI.cc +++ b/src/plotcraft/gui/PlotSaleGUI.cc @@ -18,7 +18,7 @@ void PlotSaleGUI(Player& player, PlotMetadataPtr pt) { fm.appendButton("取消出售", "textures/ui/cancel", "path", [pt](Player& pl) { bool const ok = pt->setSaleStatus(false, 0); if (ok) sendText(pl, "出售已取消"); - else sendText(pl, "出售取消失败"); + else sendText(pl, "出售取消失败"); }); } else { fm.setContent("此地皮没有出售,无法查询出售信息。"); @@ -52,24 +52,24 @@ void _sellPlotAndEditPrice(Player& player, PlotMetadataPtr pt, bool edit) { string const pr = std::get(dt->at("pr")); // 输入框只能输入字符串 if (!std::regex_match(pr, std::regex("^\\d+$"))) { - sendText(pl, "价格必须为整数"); + sendText(pl, "价格必须为整数"); return; } int const p = std::stoi(pr); if (p <= 0) { - sendText(pl, "价格必须大于0"); + sendText(pl, "价格必须大于0"); return; } if (edit) { bool const ok = pt->setSalePrice(p); if (ok) sendText(pl, "出售价格已修改"); - else sendText(pl, "出售价格修改失败"); + else sendText(pl, "出售价格修改失败"); } else { bool const ok = pt->setSaleStatus(true, p); if (ok) sendText(pl, "出售成功"); - else sendText(pl, "出售失败"); + else sendText(pl, "出售失败"); } }); } diff --git a/src/plotcraft/gui/PlotShareGUI.cc b/src/plotcraft/gui/PlotShareGUI.cc index 8b3a892..47bdce1 100644 --- a/src/plotcraft/gui/PlotShareGUI.cc +++ b/src/plotcraft/gui/PlotShareGUI.cc @@ -16,7 +16,7 @@ void PlotShareGUI(Player& player, PlotMetadataPtr pt) { fm.appendButton("清除所有共享者", "textures/ui/recap_glyph_color_2x", "path", [pt](Player& pl) { bool const ok = pt->resetSharedPlayers(); if (ok) sendText(pl, "共享信息已清除"); - else sendText(pl, "共享信息清除失败"); + else sendText(pl, "共享信息清除失败"); }); fm.appendButton("添加共享者", "textures/ui/color_plus", "path", [pt](Player& pl) { _addSharePlayer(pl, pt); }); @@ -46,7 +46,7 @@ void PlotShareGUI(Player& player, PlotMetadataPtr pt) { bool const ok = pt->delSharedPlayer(si.mSharedPlayer); if (ok) PlotShareGUI(pl, pt); else - sendText( + sendText( pl, "删除玩家 {} 的共享权限失败", ndb->getPlayerName(si.mSharedPlayer) @@ -109,7 +109,7 @@ void _addSharePlayer(Player& player, PlotMetadataPtr pt) { } if (ok) sendText(pl, "共享权限添加成功"); - else sendText(pl, "共享权限添加失败"); + else sendText(pl, "共享权限添加失败"); }); } diff --git a/src/plotcraft/gui/PlotShopGUI.cc b/src/plotcraft/gui/PlotShopGUI.cc index 845f738..a173f6d 100644 --- a/src/plotcraft/gui/PlotShopGUI.cc +++ b/src/plotcraft/gui/PlotShopGUI.cc @@ -9,7 +9,7 @@ void PlotShopGUI(Player& player) { fm.setContent("PlotCraft > 地皮商店(玩家出售)"); - auto* impl = &data::PlotBDStorage::getInstance(); + auto* impl = &data::PlotDBStorage::getInstance(); auto sls = impl->getSaleingPlots(); for (auto const& sl : sls) { @@ -41,7 +41,7 @@ void _plotShopShowPlot(Player& player, PlotMetadataPtr pt) { fm.appendButton("购买地皮", "textures/ui/confirm", "path", [pt](Player& pl) { _buyPlot(pl, pt); }); fm.appendButton("传送到此地皮", "textures/ui/send_icon", "path", [pt](Player& pl) { - PlotPos pps{pt->getX(), pt->getZ()}; + PPos pps{pt->getX(), pt->getZ()}; pl.teleport(pps.getSafestPos(), getPlotDimensionId()); }); @@ -53,26 +53,26 @@ void _plotShopShowPlot(Player& player, PlotMetadataPtr pt) { void _buyPlot(Player& player, PlotMetadataPtr pt) { - auto* impl = &data::PlotBDStorage::getInstance(); + auto* impl = &data::PlotDBStorage::getInstance(); auto& cfg = config::cfg.plotWorld; if (static_cast(impl->getPlots(player.getUuid().asString()).size()) >= cfg.maxBuyPlotCount) { - sendText(player, "你已经购买了太多地皮,无法购买新的地皮。"); + sendText(player, "你已经购买了太多地皮,无法购买新的地皮。"); return; } - auto* ms = &Moneys::getInstance(); + auto* ms = &EconomySystem::getInstance(); bool const hasOwner = !pt->getPlotOwner().empty(); bool const hasSale = pt->isSale(); bool const fromSale = hasOwner && hasSale; // 是否从出售地皮购买(玩家) int const price = fromSale ? pt->getSalePrice() : cfg.buyPlotPrice; if (hasOwner && !hasSale) { - sendText(player, "这个地皮没有出售,无法购买。"); + sendText(player, "这个地皮没有出售,无法购买。"); return; } - if (pt->getPlotOwner() == player.getUuid().asString()) { - sendText(player, "你不能购买自己的地皮。"); + if (pt->isOwner(player.getUuid().asString())) { + sendText(player, "你不能购买自己的地皮。"); return; } @@ -96,7 +96,7 @@ void _buyPlot(Player& player, PlotMetadataPtr pt) { return; } - if (ms->reduceMoney(pl, price)) { + if (ms->reduce(pl, price)) { pev::PlayerBuyPlotAfter ev{&pl, pt, price}; auto& bus = ll::event::EventBus::getInstance(); if (fromSale) { @@ -109,7 +109,7 @@ void _buyPlot(Player& player, PlotMetadataPtr pt) { // 把扣除的经济转移给出售者 auto plptr = ll::service::getLevel()->getPlayer(pt->getPlotOwner()); if (plptr) { - ms->addMoney(plptr, newPrice); // 出售者(当前地皮主人)在线 + ms->add(*plptr, newPrice); // 出售者(当前地皮主人)在线 sendText( plptr, "玩家 {} 购买了您出售的地皮 {}, 经济 +{}", @@ -123,14 +123,14 @@ void _buyPlot(Player& player, PlotMetadataPtr pt) { bus.publish(ev); sendText(pl, "地皮购买成功"); - } else sendText(pl, "地皮购买失败"); + } else sendText(pl, "地皮购买失败"); } else { - PlotPos ps{pt->getX(), pt->getZ()}; + PPos ps{pt->getX(), pt->getZ()}; bool const ok = impl->addPlot(ps.getPlotID(), pl.getUuid().asString(), pt->getX(), pt->getZ()); if (ok) { bus.publish(ev); sendText(pl, "地皮购买成功"); - } else sendText(pl, "地皮购买失败"); + } else sendText(pl, "地皮购买失败"); } } }); diff --git a/src/plotcraft/gui/PluginSettingGUI.cc b/src/plotcraft/gui/PluginSettingGUI.cc index f60172d..cdbe91c 100644 --- a/src/plotcraft/gui/PluginSettingGUI.cc +++ b/src/plotcraft/gui/PluginSettingGUI.cc @@ -3,11 +3,11 @@ namespace plo::gui { void PluginSettingGUI(Player& player) { - auto* impl = &data::PlotBDStorage::getInstance(); + auto* impl = &data::PlotDBStorage::getInstance(); auto* cfg = &config::cfg.switchDim; if (!impl->isAdmin(player.getUuid().asString())) { - sendText(player, "你没有权限执行此操作"); + sendText(player, "你没有权限执行此操作"); return; } @@ -17,7 +17,7 @@ void PluginSettingGUI(Player& player) { fm.appendButton("设置当前位置为主世界安全坐标", "textures/ui/Wrenches1", "path", [cfg](Player& pl) { if (pl.getDimensionId() != 0) { - sendText(pl, "你必须在主世界才能执行此操作"); + sendText(pl, "你必须在主世界才能执行此操作"); return; } auto const ps = pl.getPosition(); @@ -30,7 +30,7 @@ void PluginSettingGUI(Player& player) { fm.appendButton("设置当前位置为地皮世界安全坐标", "textures/ui/Wrenches1", "path", [cfg](Player& pl) { if (pl.getDimensionId() != getPlotDimensionId()) { - sendText(pl, "你必须在地皮世界才能执行此操作"); + sendText(pl, "你必须在地皮世界才能执行此操作"); return; } auto const ps = pl.getPosition(); diff --git a/src/plotcraft/utils/Debugger.h b/src/plotcraft/utils/Debugger.h new file mode 100644 index 0000000..f878747 --- /dev/null +++ b/src/plotcraft/utils/Debugger.h @@ -0,0 +1,7 @@ +#pragma once + +#ifdef DEBUG +#define debugger(...) std::cout << "[Debug] " << __VA_ARGS__ << std::endl; +#else +#define debugger(...) ((void)0) +#endif diff --git a/src/plotcraft/utils/EconomySystem.cc b/src/plotcraft/utils/EconomySystem.cc new file mode 100644 index 0000000..72e00e7 --- /dev/null +++ b/src/plotcraft/utils/EconomySystem.cc @@ -0,0 +1,191 @@ +#include "plotcraft/utils/EconomySystem.h" +#include "LLMoney.h" +#include "ll/api/form/SimpleForm.h" +#include "ll/api/i18n/I18n.h" +#include "ll/api/service/Bedrock.h" +#include "mc/deps/core/mce/UUID.h" +#include "mc/world/actor/player/PlayerScoreSetFunction.h" +#include "mc/world/level/Level.h" +#include "mc/world/scores/ScoreInfo.h" +#include "plotcraft/utils/Mc.h" +#include "plotcraft/utils/Utils.h" +#include +#include +#include +#include +#include +#include + + +// disable warning C4244: conversion from 'double' to 'int', possible loss of data +#pragma warning(disable : 4244) + +namespace plo::utils { +using ll::i18n_literals::operator""_tr; +using SimpleForm = ll::form::SimpleForm; +using namespace plo::mc; + + +int ScoreBoard_Get(Player& player, string const& scoreName) { + Scoreboard& scoreboard = ll::service::getLevel()->getScoreboard(); + Objective* obj = scoreboard.getObjective(scoreName); + if (!obj) { + sendText(player, "[Moneys] 插件错误: 找不到指定的计分板: {}"_tr(scoreName)); + return 0; + } + ScoreboardId const& id = scoreboard.getScoreboardId(player); + if (!id.isValid()) { + scoreboard.createScoreboardId(player); + } + return obj->getPlayerScore(id).mScore; +} + +bool ScoreBoard_Set(Player& player, int score, string const& scoreName) { + Scoreboard& scoreboard = ll::service::getLevel()->getScoreboard(); + Objective* obj = scoreboard.getObjective(scoreName); + if (!obj) { + sendText(player, "[Moneys] 插件错误: 找不到指定的计分板: "_tr(scoreName)); + return false; + } + const ScoreboardId& id = scoreboard.getScoreboardId(player); + if (!id.isValid()) { + scoreboard.createScoreboardId(player); + } + bool isSuccess = false; + scoreboard.modifyPlayerScore(isSuccess, id, *obj, score, PlayerScoreSetFunction::Set); + return isSuccess; +} + +bool ScoreBoard_Add(Player& player, int score, string const& scoreName) { + Scoreboard& scoreboard = ll::service::getLevel()->getScoreboard(); + Objective* obj = scoreboard.getObjective(scoreName); + if (!obj) { + sendText(player, "[Moneys] 插件错误: 找不到指定的计分板: "_tr(scoreName)); + return false; + } + const ScoreboardId& id = scoreboard.getScoreboardId(player); + if (!id.isValid()) { + scoreboard.createScoreboardId(player); + } + bool isSuccess = false; + scoreboard.modifyPlayerScore(isSuccess, id, *obj, score, PlayerScoreSetFunction::Add); + return isSuccess; +} + +bool ScoreBoard_Reduce(Player& player, int score, string const& scoreName) { + Scoreboard& scoreboard = ll::service::getLevel()->getScoreboard(); + Objective* obj = scoreboard.getObjective(scoreName); + if (!obj) { + sendText(player, "[Moneys] 插件错误: 找不到指定的计分板: "_tr(scoreName)); + return false; + } + const ScoreboardId& id = scoreboard.getScoreboardId(player); + if (!id.isValid()) { + scoreboard.createScoreboardId(player); + } + bool isSuccess = false; + scoreboard.modifyPlayerScore(isSuccess, id, *obj, score, PlayerScoreSetFunction::Subtract); + return isSuccess; +} + + +// EconomySystem +EconomySystem& EconomySystem::getInstance() { + static EconomySystem instance; + return instance; +} + +bool EconomySystem::updateConfig(EconomyConfig config) { + this->mEconomyConfig = config; + return true; +} + + +long long EconomySystem::get(Player& player) { + switch (mEconomyConfig.type) { + case EconomyType::ScoreBoard: + return ScoreBoard_Get(player, mEconomyConfig.scoreName); + case EconomyType::LegacyMoney: + return LLMoney_Get(player.getXuid()); + default: + return 0; + } +} + + +bool EconomySystem::set(Player& player, long long money) { + switch (mEconomyConfig.type) { + case EconomyType::ScoreBoard: + return ScoreBoard_Set(player, money, mEconomyConfig.scoreName); + case EconomyType::LegacyMoney: + return LLMoney_Set(player.getXuid(), money); + default: + return false; + } +} + + +bool EconomySystem::add(Player& player, long long money) { + if (!mEconomyConfig.enable) return true; // 未启用则不限制 + switch (mEconomyConfig.type) { + case EconomyType::ScoreBoard: + return ScoreBoard_Add(player, money, mEconomyConfig.scoreName); + case EconomyType::LegacyMoney: + return LLMoney_Add(player.getXuid(), money); + default: + return false; + } +} + + +bool EconomySystem::reduce(Player& player, long long money) { + if (!mEconomyConfig.enable) return true; // 未启用则不限制 + if (get(player) >= money) { // 防止玩家余额不足 + switch (mEconomyConfig.type) { + case EconomyType::ScoreBoard: + return ScoreBoard_Reduce(player, money, mEconomyConfig.scoreName); + case EconomyType::LegacyMoney: + return LLMoney_Reduce(player.getXuid(), money); + default: + return false; + } + } + // 封装提示信息 + sendLackMoneyTip(player, money); + return false; +} + + +string EconomySystem::getMoneySpendTipStr(Player& player, long long money) { + long long currentMoney = mEconomyConfig.enable ? get(player) : 0; + string prefix = "\n[§uTip§r]§r "; + + auto& name = mEconomyConfig.economicName; + + if (mEconomyConfig.enable) + return prefix + + "此操作消耗§6{0}§r:§e{1}§r | 当前§6{2}§r:§d{3}§r | 剩余§6{4}§r:§s{5}§r | {6}"_tr( + name, + money, + name, + currentMoney, + name, + currentMoney - money, + currentMoney >= money ? "§6{}§r§a充足§r"_tr(name) : "§6{}§r§c不足§r"_tr(name) + ); + else return prefix + "经济系统未启用,此操作不消耗§6{0}§r"_tr(name); +} + +void EconomySystem::sendLackMoneyTip(Player& player, long long money) { + sendText( + player, + "[Moneys] 操作失败,此操作需要{0}:{1},当前{2}:{3}"_tr( + mEconomyConfig.economicName, + money, + mEconomyConfig.economicName, + get(player) + ) + ); +} + +} // namespace plo::utils diff --git a/src/plotcraft/utils/Menu.cc b/src/plotcraft/utils/Menu.cc deleted file mode 100644 index d0327d3..0000000 --- a/src/plotcraft/utils/Menu.cc +++ /dev/null @@ -1,118 +0,0 @@ -#include "plotcraft/utils/Menu.h" -#include "ll/api/form/SimpleForm.h" -#include "plotcraft/utils/Mc.h" -#include "plotcraft/utils/Text.h" -#include "plugin/MyPlugin.h" -#include -#include -#include - - -namespace plo::utils { - -// 定义静态成员变量 -std::filesystem::path plo::utils::Menu::rootDir; -std::unordered_map> plo::utils::Menu::functions; - -std::unique_ptr Menu::fromJSON(const json& json) { - auto& logger = my_plugin::MyPlugin::getInstance().getSelf().getLogger(); - try { - auto me = std::make_unique(); - json.at("title").get_to(me->title); - json.at("content").get_to(me->content); - - int i = 0; - std::vector buttons; - for (const auto& button : json.at("buttons")) { - i++; - try { - Menu::ButtonItem it; - button.at("title").get_to(it.title); - button.at("imageData").get_to(it.imageData); - button.at("imageType").get_to(it.imageType); - button.at("callbackType").get_to(it.callbackType); - button.at("callbackRun").get_to(it.callbackRun); - buttons.push_back(it); - } catch (...) { - logger.error("Fail in Menu::fromJSON, buttons parse error in button #{}", i); - continue; - } - } - me->buttons = std::move(buttons); - return me; - } catch (...) { - logger.error("Fail in Menu::fromJSON, parse error"); - return nullptr; - } -} - -std::unique_ptr Menu::fromJsonFile(const string fileName) { - auto& logger = my_plugin::MyPlugin::getInstance().getSelf().getLogger(); - try { - std::filesystem::path path = Menu::rootDir / fileName; - if (path.extension() != ".json") { - logger.error("Fail in Menu::fromJsonFile, not a json file"); - return nullptr; - } - if (!std::filesystem::exists(path)) { - logger.error("Fail in Menu::fromJsonFile, file not exist"); - return nullptr; - } - std::ifstream file(path); - json jsonData; - file >> jsonData; - return fromJSON(jsonData); - } catch (...) { - logger.error("Fail in Menu::fromJsonFile, parse error"); - return nullptr; - } -} -void Menu::fromJsonFile(Player& player) { - auto me = fromJsonFile("index.json"); - if (me) me->sendTo(player); - else sendText(player, "Menu file not found"); -} - -bool Menu::hasFunction(const string name) { return Menu::functions.find(name) != Menu::functions.end(); } - -void Menu::sendTo(Player& player) { - using namespace ll::form; - auto& logger = my_plugin::MyPlugin::getInstance().getSelf().getLogger(); - try { - SimpleForm fm; - fm.setTitle(title); - fm.setContent(content); - - for (const auto& bt : buttons) { - - std::function callback = [bt](Player& p) { - auto& logger = my_plugin::MyPlugin::getInstance().getSelf().getLogger(); - if (bt.callbackType == "function") { - if (Menu::hasFunction(bt.callbackRun)) { - Menu::functions[bt.callbackRun](p); - } else logger.error("Fail in Menu::sendTo, function not found {}", bt.callbackRun); - } else if (bt.callbackType == "cmd") { - mc::executeCommand(bt.callbackRun, &p); - } else if (bt.callbackType == "subform") { - auto sub = Menu::fromJsonFile(bt.callbackRun); - if (sub) sub->sendTo(p); - else logger.error("Fail in Menu::sendTo, subform not found {}", bt.callbackRun); - } else { - logger.error("Fail in Menu::sendTo, unknown callback type {}", bt.callbackType); - } - }; - - if (bt.imageType == "path" || bt.imageType == "url") { - fm.appendButton(bt.title, bt.imageData, bt.imageType, callback); - } else { - fm.appendButton(bt.title, callback); - } - } - - fm.sendTo(player); - } catch (...) { - logger.error("Fail in Menu::sendTo, unknown error"); - } -} - -} // namespace plo::utils \ No newline at end of file diff --git a/src/plotcraft/utils/Moneys.cc b/src/plotcraft/utils/Moneys.cc deleted file mode 100644 index 9b47a24..0000000 --- a/src/plotcraft/utils/Moneys.cc +++ /dev/null @@ -1,265 +0,0 @@ -#include "plotcraft/utils/Moneys.h" -#include "LLMoney.h" -#include "ll/api/form/SimpleForm.h" -#include "ll/api/i18n/I18n.h" -#include "ll/api/service/Bedrock.h" -#include "mc/deps/core/mce/UUID.h" -#include "mc/world/actor/player/PlayerScoreSetFunction.h" -#include "mc/world/level/Level.h" -#include "mc/world/scores/ScoreInfo.h" -#include "plotcraft/utils/Moneys.h" -#include "plotcraft/utils/Text.h" -#include "plotcraft/utils/Utils.h" -#include -#include -#include -#include -#include -#include - - -// disable warning C4244: conversion from 'double' to 'int', possible loss of data -#pragma warning(disable : 4244) - -namespace plo::utils { -using ll::i18n_literals::operator""_tr; -using SimpleForm = ll::form::SimpleForm; - - -// ScoreBoard -ScoreBoardMoney::ScoreBoardMoney(const string& scoreName) { this->mScoreName = string(scoreName); } - -void ScoreBoardMoney::setScoreName(const string& scoreName) { this->mScoreName = string(scoreName); } - -int ScoreBoardMoney::getScore(Player& player) { - Scoreboard& scoreboard = ll::service::getLevel()->getScoreboard(); - Objective* obj = scoreboard.getObjective(this->mScoreName); - if (!obj) { - sendText(player, "[Moneys] 插件错误: 找不到指定的计分板: {}"_tr(this->mScoreName)); - std::runtime_error("Fail in ScoreBoardMoney::getScore: Objective not found"); - return 0; - } - const ScoreboardId& id = scoreboard.getScoreboardId(player); - if (!id.isValid()) { - scoreboard.createScoreboardId(player); - } - return obj->getPlayerScore(id).mScore; -} -int ScoreBoardMoney::getScore(Player* player) { return this->getScore(*player); } -int ScoreBoardMoney::getScore(mce::UUID uuid) { return getScore(ll::service::getLevel()->getPlayer(uuid)); } -int ScoreBoardMoney::getScore(const string& realName) { return getScore(ll::service::getLevel()->getPlayer(realName)); } - - -bool ScoreBoardMoney::setScore(Player& player, int score) { - Scoreboard& scoreboard = ll::service::getLevel()->getScoreboard(); - Objective* obj = scoreboard.getObjective(this->mScoreName); - if (!obj) { - sendText(player, "[Moneys] 插件错误: 找不到指定的计分板: "_tr(this->mScoreName)); - std::runtime_error("Fail in ScoreBoardMoney::setScore: Objective not found"); - return false; - } - const ScoreboardId& id = scoreboard.getScoreboardId(player); - if (!id.isValid()) { - scoreboard.createScoreboardId(player); - } - bool isSuccess = false; - scoreboard.modifyPlayerScore(isSuccess, id, *obj, score, PlayerScoreSetFunction::Set); - return isSuccess; -} -bool ScoreBoardMoney::setScore(Player* player, int score) { return this->setScore(*player, score); } -bool ScoreBoardMoney::setScore(mce::UUID uuid, int score) { - return setScore(ll::service::getLevel()->getPlayer(uuid), score); -} -bool ScoreBoardMoney::setScore(const string& realName, int score) { - return setScore(ll::service::getLevel()->getPlayer(realName), score); -} - - -bool ScoreBoardMoney::addScore(Player& player, int score) { - Scoreboard& scoreboard = ll::service::getLevel()->getScoreboard(); - Objective* obj = scoreboard.getObjective(this->mScoreName); - if (!obj) { - sendText(player, "[Moneys] 插件错误: 找不到指定的计分板: "_tr(this->mScoreName)); - std::runtime_error("Fail in ScoreBoardMoney::addScore: Objective not found"); - return false; - } - const ScoreboardId& id = scoreboard.getScoreboardId(player); - if (!id.isValid()) { - scoreboard.createScoreboardId(player); - } - bool isSuccess = false; - scoreboard.modifyPlayerScore(isSuccess, id, *obj, score, PlayerScoreSetFunction::Add); - return isSuccess; -} -bool ScoreBoardMoney::addScore(Player* player, int score) { return this->addScore(*player, score); } -bool ScoreBoardMoney::addScore(mce::UUID uuid, int score) { - return addScore(ll::service::getLevel()->getPlayer(uuid), score); -} -bool ScoreBoardMoney::addScore(const string& realName, int score) { - return addScore(ll::service::getLevel()->getPlayer(realName), score); -} - - -bool ScoreBoardMoney::reduceScore(Player& player, int score) { - Scoreboard& scoreboard = ll::service::getLevel()->getScoreboard(); - Objective* obj = scoreboard.getObjective(this->mScoreName); - if (!obj) { - sendText(player, "[Moneys] 插件错误: 找不到指定的计分板: "_tr(this->mScoreName)); - std::runtime_error("Fail in ScoreBoardMoney::reduceScore: Objective not found"); - return false; - } - const ScoreboardId& id = scoreboard.getScoreboardId(player); - if (!id.isValid()) { - scoreboard.createScoreboardId(player); - } - bool isSuccess = false; - scoreboard.modifyPlayerScore(isSuccess, id, *obj, score, PlayerScoreSetFunction::Subtract); - return isSuccess; -} -bool ScoreBoardMoney::reduceScore(Player* player, int score) { return this->reduceScore(*player, score); } -bool ScoreBoardMoney::reduceScore(mce::UUID uuid, int score) { - return reduceScore(ll::service::getLevel()->getPlayer(uuid), score); -} -bool ScoreBoardMoney::reduceScore(const string& realName, int score) { - return reduceScore(ll::service::getLevel()->getPlayer(realName), score); -} - - -// Moneys -Moneys& Moneys::getInstance() { - static Moneys instance; - return instance; -} - -void Moneys::throwUnknownType(Player* player) { - if (player) { - sendText(player, "[Moneys] 插件错误: 未知的货币类型"_tr()); - } - std::runtime_error("Fail in Moneys::throwUnknownType: Unknown currency type"); -} - -bool Moneys::updateConfig(MoneysConfig config) { - this->mIsEnable = config.Enable; - this->mMoneyName = config.MoneyName; - this->mType = config.MoneyType; - setScoreName(config.ScoreName); - return true; -} - - -long long Moneys::getMoney(Player& player) { - switch (mType) { - case MoneysType::ScoreBoard: - return getScore(player); - case MoneysType::LLMoney: - return LLMoney_Get(player.getXuid()); - default: - throwUnknownType(&player); - return 0; - } -} -long long Moneys::getMoney(Player* player) { return this->getMoney(*player); } -long long Moneys::getMoney(mce::UUID uuid) { return getMoney(ll::service::getLevel()->getPlayer(uuid)); } -long long Moneys::getMoney(const string& realName) { return getMoney(ll::service::getLevel()->getPlayer(realName)); } - - -bool Moneys::setMoney(Player& player, long long money) { - switch (mType) { - case MoneysType::ScoreBoard: - return setScore(player, money); - case MoneysType::LLMoney: - return LLMoney_Set(player.getXuid(), money); - default: - throwUnknownType(&player); - return false; - } -} -bool Moneys::setMoney(Player* player, long long money) { return this->setMoney(*player, money); } -bool Moneys::setMoney(mce::UUID uuid, long long money) { - return setMoney(ll::service::getLevel()->getPlayer(uuid), money); -} -bool Moneys::setMoney(const string& realName, long long money) { - return setMoney(ll::service::getLevel()->getPlayer(realName), money); -} - - -bool Moneys::addMoney(Player& player, long long money) { - if (!mIsEnable) return true; // 未启用则不限制 - switch (mType) { - case MoneysType::ScoreBoard: - return addScore(player, money); - case MoneysType::LLMoney: - return LLMoney_Add(player.getXuid(), money); - default: - throwUnknownType(&player); - return false; - } -} -bool Moneys::addMoney(Player* player, long long money) { return this->addMoney(*player, money); } -bool Moneys::addMoney(mce::UUID uuid, long long money) { - return addMoney(ll::service::getLevel()->getPlayer(uuid), money); -} -bool Moneys::addMoney(const string& realName, long long money) { - return addMoney(ll::service::getLevel()->getPlayer(realName), money); -} - - -bool Moneys::reduceMoney(Player& player, long long money) { - if (!mIsEnable) return true; // 未启用则不限制 - if (getMoney(player) >= money) { // 防止玩家余额不足 - switch (mType) { - case MoneysType::ScoreBoard: - return reduceScore(player, money); - case MoneysType::LLMoney: - return LLMoney_Reduce(player.getXuid(), money); - default: - throwUnknownType(&player); - return false; - } - } - // 封装提示信息 - sendMoneySpendTip(player, money); - return false; -} -bool Moneys::reduceMoney(Player* player, long long money) { return this->reduceMoney(*player, money); } -bool Moneys::reduceMoney(mce::UUID uuid, long long money) { - return reduceMoney(ll::service::getLevel()->getPlayer(uuid), money); -} -bool Moneys::reduceMoney(const string& realName, long long money) { - return reduceMoney(ll::service::getLevel()->getPlayer(realName), money); -} - - -string Moneys::getMoneySpendTipStr(Player& player, long long money) { - long long currentMoney = mIsEnable ? getMoney(player) : 0; - string prefix = "\n§l[§uTip§r§l]§r "; - - if (mIsEnable) - return prefix - + "此操作消耗§6{0}§r:§e{1}§r | 当前§6{2}§r:§d{3}§r | 剩余§6{4}§r:§s{5}§r | {6}"_tr( - mMoneyName, - money, - mMoneyName, - currentMoney, - mMoneyName, - currentMoney - money, - currentMoney >= money ? "§6{}§r§a充足§r"_tr(mMoneyName) : "§6{}§r§c不足§r"_tr(mMoneyName) - ); - else return prefix + "经济系统未启用,此操作不消耗§6{0}§r"_tr(mMoneyName); -} -string Moneys::getMoneySpendTipStr(Player* player, long long money) { return getMoneySpendTipStr(*player, money); } -string Moneys::getMoneySpendTipStr(mce::UUID uuid, long long money) { - return getMoneySpendTipStr(ll::service::getLevel()->getPlayer(uuid), money); -} -string Moneys::getMoneySpendTipStr(const string& realName, long long money) { - return getMoneySpendTipStr(ll::service::getLevel()->getPlayer(realName), money); -} - -void Moneys::sendMoneySpendTip(Player& player, long long money) { - sendText( - player, - "[Moneys] 操作失败,此操作需要{0}:{1},当前{2}:{3}"_tr(mMoneyName, money, mMoneyName, getMoney(player)) - ); -} - -} // namespace plo::utils diff --git a/src/plugin/MyPlugin.cpp b/src/plugin/MyPlugin.cpp index 6a1d6e5..0c9cd2f 100644 --- a/src/plugin/MyPlugin.cpp +++ b/src/plugin/MyPlugin.cpp @@ -4,11 +4,13 @@ #include #include +#include "fmt/color.h" #include "ll/api/event/EventBus.h" #include "ll/api/event/server/ServerStartedEvent.h" #include "ll/api/i18n/I18n.h" #include "ll/api/mod/RegisterHelper.h" #include "ll/api/service/Bedrock.h" +#include "ll/api/utils/SystemUtils.h" #include "mc/server/ServerLevel.h" #include "mc/server/commands/CommandContext.h" #include "mc/server/commands/CommandOutputType.h" @@ -20,15 +22,13 @@ #include "plotcraft/command/Command.h" #include "plotcraft/core/PlotDimension.h" #include "plotcraft/data/PlayerNameDB.h" -#include "plotcraft/data/PlotBDStorage.h" +#include "plotcraft/data/PlotDBStorage.h" #include "plotcraft/event/Event.h" +#include "plotcraft/utils/EconomySystem.h" #include "plotcraft/utils/Mc.h" -#include "plotcraft/utils/Moneys.h" +#include - -#ifdef REMOTE_API -#include "remote/Remote.h" -#endif +#include "plotcraft/core/TemplateManager.h" #if !defined(OVERWORLD) #include "more_dimensions/api/dimension/CustomDimensionManager.h" @@ -44,7 +44,13 @@ static std::unique_ptr instance; MyPlugin& MyPlugin::getInstance() { return *instance; } bool MyPlugin::load() { - auto& logger = getSelf().getLogger(); + auto& self = getSelf(); + auto& logger = self.getLogger(); + + if (ll::sys_utils::isStdoutSupportAnsi()) { + logger.title = fmt::format(fmt::fg(fmt::color::light_green), logger.title); + } + logger.info(R"( )"); logger.info(R"( ____ __ __ ______ ____ __ )"); logger.info(R"( / __ \ / /____ / /_ / ____/_____ ____ _ / __// /_)"); @@ -54,41 +60,45 @@ bool MyPlugin::load() { logger.info(R"( )"); logger.info(R"( ---- Author: engsr6982 ---- )"); logger.info(R"( )"); - logger.info("加载中..."); + logger.info("Loading..."); + logger.info("编译参数: {}", BuildVersionInfo); + logger.info("创建 data 文件夹..."); - logger.info("编译版本信息: {}", BuildVersionInfo); + auto& dataDir = self.getDataDir(); + auto& langDir = self.getLangDir(); - logger.info("尝试创建必要的文件夹..."); - - if (!fs::exists(getSelf().getDataDir())) { - fs::create_directories(getSelf().getDataDir()); - } + if (!fs::exists(dataDir)) fs::create_directories(dataDir); - logger.info("尝试加载数据..."); + logger.info("加载数据..."); plo::config::loadConfig(); - ll::i18n::load(getSelf().getLangDir()); - plo::data::PlotBDStorage::getInstance().load(); + ll::i18n::load(langDir); + plo::data::PlotDBStorage::getInstance().load(); plo::data::PlayerNameDB::getInstance().initPlayerNameDB(); plo::EconomyQueue::getInstance().load(); - - plo::utils::Moneys::getInstance().updateConfig(plo::config::cfg.moneys); - - -#ifdef REMOTE_API - plo::remote::exportPLAPI(); // 导出PLAPI - plo::remote::exportPLEvent(); // 导出PLEvent - logger.info("RemoteCall API 已导出。"); -#endif - + plo::utils::EconomySystem::getInstance().updateConfig(plo::config::cfg.economy); return true; } bool MyPlugin::enable() { - auto& logger = getSelf().getLogger(); + auto& self = getSelf(); + auto& logger = self.getLogger(); logger.info("Enabling..."); - logger.info("尝试注册 命令、维度、事件..."); + logger.info("注册 命令、事件..."); + + auto& cfg = plo::config::cfg; + auto& configDir = self.getConfigDir(); + if (cfg.generator.type == plo::config::PlotGeneratorType::Template) { + logger.info("检测到使用模板生成器,加载地皮模板..."); + if (plo::core::TemplateManager::loadTemplate((configDir / cfg.generator.templateFile).string())) { + logger.info("模板 \"{}\" 已加载", cfg.generator.templateFile); + } else { + logger.error("加载模板 \"{}\" 失败,请检查配置文件", cfg.generator.templateFile); + return false; + } + }; + #ifdef DEBUG plo::mc::executeCommand("gamerule showcoordinates true"); @@ -100,7 +110,7 @@ bool MyPlugin::enable() { plo::event::registerEventListener(); // 注册事件监听器 plo::command::registerCommand(); // 注册命令 - plo::data::PlotBDStorage::getInstance().tryStartSaveThread(); // 尝试启动自动保存线程 + plo::data::PlotDBStorage::getInstance().tryStartSaveThread(); // 尝试启动自动保存线程 return true; } @@ -110,7 +120,7 @@ bool MyPlugin::disable() { logger.info("Disabling..."); logger.warn("正在保存数据,请不要强制关闭进程..."); - plo::data::PlotBDStorage::getInstance().save(); + plo::data::PlotDBStorage::getInstance().save(); plo::event::unRegisterEventListener(); diff --git a/src/remote/PLAPI.cc b/src/remote/PLAPI.cc deleted file mode 100644 index 5f365df..0000000 --- a/src/remote/PLAPI.cc +++ /dev/null @@ -1,86 +0,0 @@ -#ifdef REMOTE_API -#include "Remote.h" -#include "mc/world/level/dimension/VanillaDimensions.h" -#include "plotcraft/data/PlotBDStorage.h" -#include "plotcraft/utils/JsonHelper.h" - -namespace plo::remote { - - -void exportPLAPI() { - using namespace RemoteCall; - using FloatPos = std::pair; - string const sp = "PLAPI"; - - exportAs(sp, "getPlotWorldDimid", []() -> int { return getPlotDimensionId(); }); - - exportAs(sp, "getDimidFromString", [](string const& str) -> int { return VanillaDimensions::fromString(str); }); - - exportAs(sp, "PlotPos_toString", [](int x, int z) -> string { return PlotPos{x, z}.toString(); }); - - exportAs(sp, "PlotPos_getPlotID", [](int x, int z) -> string { return PlotPos{x, z}.getPlotID(); }); - - exportAs(sp, "PlotPos_toDebug", [](int x, int z) -> string { return PlotPos{x, z}.toDebug(); }); - - exportAs(sp, "PlotPos_isPosInPlot", [](int px, int pz, FloatPos const& pos) -> bool { - return PlotPos{px, pz}.isPosInPlot(pos.first); - }); - - exportAs(sp, "PlotPos_getSafestPos", [](int px, int pz) -> FloatPos { - return std::make_pair(PlotPos{px, pz}.getSafestPos(), getPlotDimensionId()); - }); - - exportAs(sp, "PlotPos_isPosOnBorder", [](int x, int z, FloatPos const& pos) -> bool { - return PlotPos{x, z}.isPosOnBorder(pos.first); - }); - - exportAs(sp, "PlotPos_getMin", [](int x, int z) -> FloatPos { - return std::make_pair(PlotPos{x, z}.getMin(), getPlotDimensionId()); - }); - - exportAs(sp, "PlotPos_getMax", [](int x, int z) -> FloatPos { - return std::make_pair(PlotPos{x, z}.getMax(), getPlotDimensionId()); - }); - - using JS_PlotPos_Constructor = std::vector; - exportAs(sp, "getPlotPosByPos", [](FloatPos const& pos) -> JS_PlotPos_Constructor { - JS_PlotPos_Constructor ret; - PlotPos pps{pos.first}; - ret.push_back(pps.x); - ret.push_back(pps.z); - ret.push_back(pps.isValid()); - return ret; - }); - - exportAs(sp, "getPlotPosByXZ", [](int x, int z) -> JS_PlotPos_Constructor { - JS_PlotPos_Constructor ret; - PlotPos pps{x, z}; - ret.push_back(pps.x); - ret.push_back(pps.z); - ret.push_back(pps.isValid()); - return ret; - }); - - using namespace data; - exportAs(sp, "getPlayerPermission", [](string const& uuid, PlotID const& pid, bool ignoreAdmin) -> int { - auto& db = data::PlotBDStorage::getInstance(); - PlotPermission perm = db.getPlayerPermission(uuid, pid, ignoreAdmin); - return static_cast(perm); - }); - - exportAs(sp, "PlotMetadata_getPermissionTableConst", [](PlotID const& pid) -> string { - auto& db = data::PlotBDStorage::getInstance(); - if (db.hasPlot(pid)) { - auto meta = db.getPlot(pid); - if (meta) { - return utils::JsonHelper::structToJsonString(meta->getPermissionTableConst()); - } - } - return ""; - }); -} - - -} // namespace plo::remote - -#endif // REMOTE_API \ No newline at end of file diff --git a/src/remote/PLEvent.cc b/src/remote/PLEvent.cc deleted file mode 100644 index 4fe7d91..0000000 --- a/src/remote/PLEvent.cc +++ /dev/null @@ -1,59 +0,0 @@ -#ifdef REMOTE_API -#include "Remote.h" -#include "ll/api/event/EventBus.h" -#include "ll/api/utils/HashUtils.h" -#include "mc/world/actor/player/Player.h" -#include "plotcraft/event/PlotEvents.h" - - -using ll::hash_literals::operator""_h; -using namespace ll::hash_utils; -using namespace RemoteCall; -using FloatPos = std::pair; -using JS_PlotPos_Constructor = std::vector; // x,z,isValid - - -namespace plo::remote { - -void exportPLEvent() { - string const sp = "PLAPI"; - auto* bus = &ll::event::EventBus::getInstance(); - - - exportAs(sp, "CallPLEvent", [bus](string const& eventName, string const& funcName) -> bool { - if (hasFunc(eventName, funcName)) { - switch (doHash(eventName)) { - case "PlayerEnterPlot"_h: { - auto call = importAs(eventName, funcName); - bus->emplaceListener([call](event::PlayerEnterPlot& ev) { - auto pos = ev.getPos(); - JS_PlotPos_Constructor cons = {pos.x, pos.z, pos.isValid()}; - - try { - call(ev.getPlayer(), cons); - } catch (...) {} - }); - return true; - } - case "PlayerLeavePlot"_h: { - auto call = importAs(eventName, funcName); - bus->emplaceListener([call](event::PlayerLeavePlot& ev) { - auto pos = ev.getPos(); - JS_PlotPos_Constructor cons = {pos.x, pos.z, pos.isValid()}; - - try { - call(ev.getPlayer(), cons); - } catch (...) {} - }); - return true; - } - } - } - return false; - }); -} - - -} // namespace plo::remote - -#endif // REMOTE_API \ No newline at end of file diff --git a/src/remote/Remote.h b/src/remote/Remote.h deleted file mode 100644 index 6b1eb52..0000000 --- a/src/remote/Remote.h +++ /dev/null @@ -1,38 +0,0 @@ -#ifdef REMOTE_API -#include "mc/enums/GameType.h" -#include "mc/math/Vec3.h" -#include "mc/world/gamemode/GameMode.h" -#include "mc/world/level/dimension/Dimension.h" -#include "mc/world/level/dimension/VanillaDimensions.h" -#include "plotcraft/Config.h" -#include "plotcraft/EconomyQueue.h" -#include "plotcraft/PlotPos.h" -#include "plotcraft/utils/Text.h" -#include "plotcraft/utils/Utils.h" -#include -#include - -#include "plotcraft/core/CoreUtils.h" - -#ifdef _MSC_VER -#pragma warning(push) -#pragma warning(disable : 4702 4172) -#endif -#include "RemoteCallAPI.h" -#ifdef _MSC_VER -#pragma warning(pop) -#endif - -using namespace plo::core_utils; - -namespace plo::remote { - - -void exportPLAPI(); - -void exportPLEvent(); - - -} // namespace plo::remote - -#endif // REMOTE_API \ No newline at end of file diff --git a/src/remote/lib/PLAPI.js b/src/remote/lib/PLAPI.js deleted file mode 100644 index 9b59e15..0000000 --- a/src/remote/lib/PLAPI.js +++ /dev/null @@ -1,189 +0,0 @@ -/// -/// - -// 使用: -// let { PLAPI } = require("./PlotCraft/lib/PLAPI.js"); -// PLAPI.getPlotWorldDimid(); -// -// 注意: -// 本文件仅提供部分基础API。 -// 如果你需要更强大API,请使用SDK,或者自行export提交pr - -const _Remote_ = { - getPlotWorldDimid: ll.imports("PLAPI", "getPlotWorldDimid"), - getDimidFromString: ll.imports("PLAPI", "getDimidFromString"), - PlotPos_toString: ll.imports("PLAPI", "PlotPos_toString"), - PlotPos_toDebug: ll.imports("PLAPI", "PlotPos_toDebug"), - PlotPos_isPosInPlot: ll.imports("PLAPI", "PlotPos_isPosInPlot"), - PlotPos_getSafestPos: ll.imports("PLAPI", "PlotPos_getSafestPos"), - PlotPos_isPosOnBorder: ll.imports("PLAPI", "PlotPos_isPosOnBorder"), - PlotPos_getPlotID: ll.imports("PLAPI", "PlotPos_getPlotID"), - getPlayerPermission: ll.imports("PLAPI", "getPlayerPermission"), - PlotPos_getMin: ll.imports("PLAPI", "PlotPos_getMin"), - PlotPos_getMax: ll.imports("PLAPI", "PlotPos_getMax"), - getPlotPosByPos: ll.imports("PLAPI", "getPlotPosByPos"), - getPlotPosByXZ: ll.imports("PLAPI", "getPlotPosByXZ"), - PlotMetadata_getPermissionTableConst: ll.imports( - "PLAPI", - "PlotMetadata_getPermissionTableConst" - ), -}; - -class PLAPI { - /** - * 获取地皮世界维度 ID - * @returns {number} - */ - static getPlotWorldDimid() { - return _Remote_.getPlotWorldDimid(); - } - - /** - * 使用名称获取维度id - * @param {string} name - */ - static getDimidFromString(name) { - return _Remote_.getDimidFromString(name); - } - - /** - * @param {number} x - * @param {number} y - * @param {number} z - * @returns {FloatPos} - */ - static getPlotWorldFloatPos(x, y, z) { - return new FloatPos(x, y, z, this.getPlotWorldDimid()); - } - - /** - * 获取玩家权限 - * @param {string} uuid 玩家 UUID - * @param {string} plotID 地皮 ID - * @param {boolean} ignoreAdmin 忽略管理员 - * @returns {0|1|2|3} 无权限 | 共享者 | 所有者 | 管理员 - */ - static getPlayerPermission(uuid, plotID, ignoreAdmin = false) { - return _Remote_.getPlayerPermission(uuid, plotID, ignoreAdmin); - } - - /** - * 获取 PlotPos 对象 - * @param {FloatPos} pos - * @returns {PlotPos} - */ - static getPlotPosByPos(pos) { - const val = _Remote_.getPlotPosByPos(pos); - return new PlotPos(val); - } - - /** - * 获取 PlotPos 对象 - * @param {number} x - * @param {number} z - * @returns {PlotPos} - */ - static getPlotPosByXZ(x, z) { - const val = _Remote_.getPlotPosByXZ(x, z); - return new PlotPos(val); - } -} - -class PlotPos { - /** - * @param {Array} constructorArgs 构造参数 - */ - constructor(constructorArgs) { - if (constructorArgs.length !== 3) { - throw new Error( - "PlotPos constructor args length error, Please from PLAPI.getPlotPosByPos or PLAPI.getPlotPosByXZ" - ); - } - this.x = constructorArgs[0]; // x - this.z = constructorArgs[1]; // z - this.mIsVaild = constructorArgs[2]; // 是否有效 - } - - /** - * @returns {boolean} - */ - isValid() { - return this.mIsVaild; - } - - /** - * @returns {FloatPos} - */ - getMin() { - return _Remote_.PlotPos_getMin(this.x, this.z); - } - - /** - * @returns {FloatPos} - */ - getMax() { - return _Remote_.PlotPos_getMax(this.x, this.z); - } - - /** - * @returns {string} - */ - toString() { - return _Remote_.PlotPos_toString(this.x, this.z); - } - - /** - * @returns {string} - */ - getPlotID() { - return _Remote_.PlotPos_getPlotID(this.x, this.z); - } - - /** - * @returns {string} - */ - toDebug() { - return _Remote_.PlotPos_toDebug(this.x, this.z); - } - - /** - * @param {FloatPos} pos - * @returns {boolean} - */ - isPosInPlot(pos) { - return _Remote_.PlotPos_isPosInPlot(this.x, this.z, pos); - } - - /** - * @returns {FloatPos} - */ - getSafestPos() { - return _Remote_.PlotPos_getSafestPos(this.x, this.z); - } - - /** - * @param {FloatPos} pos - * @returns {boolean} - */ - isPosOnBorder(pos) { - return _Remote_.PlotPos_isPosOnBorder(this.x, this.z, pos); - } -} - -class PlotMetadata { - constructor(plotID) { - this.mPlotID = plotID; - } - - getPermissionTableConst() { - let dt = _Remote_.PlotMetadata_getPermissionTableConst(this.mPlotID); - if (dt == "") return null; - return JSON.parse(dt); - } -} - -module.exports = { - PLAPI, - PlotPos, - PlotMetadata, -}; diff --git a/src/remote/lib/PLEvent.js b/src/remote/lib/PLEvent.js deleted file mode 100644 index 2a42913..0000000 --- a/src/remote/lib/PLEvent.js +++ /dev/null @@ -1,32 +0,0 @@ -/// - -const CallEvent = ll.imports("PLAPI", "CallPLEvent"); - -let nextID = 0; -function getNextFuncName() { - return `PLEvent_${nextID++}`; -} - -class PLEvent { - constructor() { - throw new Error("Static class cannot be instantiated."); - } - - /** - * @param {string} eventName - * @param {Function} callback - */ - static on(eventName, callback) { - const funcName = getNextFuncName(); - ll.exports(callback, eventName, funcName); - const ok = CallEvent(eventName, funcName); - if (!ok) { - logger.error(`Unknow event name: ${eventName}`); - } - return ok; - } -} - -module.exports = { - PLEvent, -}; diff --git a/src/remote/lib/Type.d.ts b/src/remote/lib/Type.d.ts deleted file mode 100644 index f634b44..0000000 --- a/src/remote/lib/Type.d.ts +++ /dev/null @@ -1,114 +0,0 @@ -declare class PLAPI { - /** - * 获取地皮世界的维度ID - */ - static getPlotWorldDimid(): number; - - /** - * 通过维度名称获取维度ID - * @param name 维度名称 - */ - static getDimidFromString(name: string): number; - - /** - * 获取地皮世界的坐标 - * @param x x坐标 - * @param y y坐标 - * @param z z坐标 - */ - static getPlotWorldFloatPos(x: number, y: number, z: number): FloatPos; - - /** - * 获取玩家权限 - * @param uuid 玩家UUID - * @param plotID 地皮ID - * @param ignoreAdmin 是否忽略管理员权限 - */ - static getPlayerPermission( - uuid: string, - plotID: string, - ignoreAdmin: boolean - ): PlotPermission; - - /** - * 通过坐标对象获取地皮坐标对象 - * @param pos 浮点坐标 - */ - static getPlotPosByPos(pos: FloatPos): PlotPos; - - /** - * 通过地皮坐标获取坐标对象 - * @param x x坐标 - * @param z z坐标 - */ - static getPlotPosByXZ(x: number, z: number): PlotPos; -} - -declare enum PlotPermission { - None = 0, - Shared = 1, - Owner = 2, - Admin = 3, -} - -declare class PlotPos { - constructor(constructorArgs: [3, number, number, number]); - - isValid(): boolean; - - getMin(): FloatPos; - - getMax(): FloatPos; - - toString(): string; - - getPlotID(): string; - - toDebug(): string; - - isPosInPlot(pos: FloatPos): boolean; - - getSafestPos(): FloatPos; - - isPosOnBorder(pos: FloatPos): boolean; -} - -declare type PlotPermissionTable = { - /**[LL] 破坏方块*/ canDestroyBlock: boolean; - /**[LL] 放置方块*/ canPlaceBlock: boolean; - /**[LL] 使用物品(右键)*/ canUseItemOn: boolean; - /**[LL] 火焰蔓延*/ canFireSpread: boolean; - /**[LL] 攻击*/ canAttack: boolean; - /**[LL] 拾取物品*/ canPickupItem: boolean; - /**[LL] 与方块交互*/ canInteractBlock: boolean; - - /**[LSE] 耕地退化*/ canFarmLandDecay: boolean; - /**[LSE] 操作展示框*/ canOperateFrame: boolean; - /**[LSE] 生物受伤*/ canMobHurt: boolean; - /**[LSE] 攻击方块*/ canAttackBlock: boolean; - /**[LSE] 操作盔甲架*/ canOperateArmorStand: boolean; - /**[LSE] 丢弃物品*/ canDropItem: boolean; - /**[LSE] 踩压压力板*/ canStepOnPressurePlate: boolean; - /**[LSE] 骑乘*/ canRide: boolean; - /**[LSE] 凋零破坏方块*/ canWitherDestroyBlock: boolean; - /**[LSE] 红石更新*/ canRedStoneUpdate: boolean; -}; - -/** - * 地皮元信息类 - */ -declare class PlotMetadata { - mPlotID: string; - - /** - * 获取地皮元信息 - * @param plotID - */ - constructor(plotID: string): PlotMetadata; - - /** - * 获取地皮权限表 - * @returns PlotPermissionTable | null - */ - getPermissionTableConst(): PlotPermissionTable | null; -} diff --git a/src/remote/lse/PlotCraft-Fixer.js b/src/remote/lse/PlotCraft-Fixer.js deleted file mode 100644 index 251b29a..0000000 --- a/src/remote/lse/PlotCraft-Fixer.js +++ /dev/null @@ -1,124 +0,0 @@ -/// -/// - -const { PLAPI, PlotPos, PlotMetadata } = require("./PlotCraft/lib/PLAPI.js"); -const { PLEvent } = require("./PlotCraft/lib/PLEvent.js"); - -logger.info("PlotCeaft-Fixer loading..."); -logger.info("Author: engsr6982"); - -// 实体爆炸 -mc.listen( - "onEntityExplode", - (source, pos, radius, maxResistance, isDestroy, isFire) => { - logger.debug("onEntityExplode"); - if (pos.dimid === PLAPI.getPlotWorldDimid()) return false; - } -); - -// 耕地退化 -mc.listen("onFarmLandDecay", (pos, ent) => { - logger.debug("onFarmLandDecay"); - if (pos.dimid != PLAPI.getPlotWorldDimid()) return; // 仅处理 PlotWorld 内的事件 - const pps = PLAPI.getPlotPosByPos(pos); - const tab = new PlotMetadata(pps.getPlotID).getPermissionTableConst(); - const valid = pps.isValid(); - - if (ent.isPlayer()) { - const pl = ent.toPlayer(); - const lv = PLAPI.getPlayerPermission(pl.uuid, pps.getPlotID()); - - if (lv >= 1 && valid) return; // 管理员 & 所有者 & 成员 & 地皮内 => 放行 - if (lv !== 3 && !valid) return false; // 非管理员 & 地皮外 => 拦截 - } - - if (!valid) return false; // 地皮外 => 拦截 - if (!tab) return false; // 全新地皮 => 拦截 - if (!tab.canFarmLandDecay) return false; // 地皮禁止耕地退化 => 拦截 -}); - -// 玩家操作展示框 -mc.listen("onUseFrameBlock", (pl, bl) => { - const pos = bl.pos; - if (pos.dimid != PLAPI.getPlotWorldDimid()) return; - - const pps = PLAPI.getPlotPosByPos(pos); - const lv = PLAPI.getPlayerPermission(pl.uuid, pps.getPlotID()); - const valid = pps.isValid(); - - if (lv >= 1) return; // 管理员 & 所有者 & 成员 => 放行 - - const tab = new PlotMetadata(pps.getPlotID).getPermissionTableConst(); - if (!valid) return false; // 地皮外 => 拦截 - if (!tab) return false; // 全新地皮 => 拦截 - if (!tab.canOperateFrame) return false; // 地皮禁止操作展示框 => 拦截 -}); - -// 活塞尝试推动方块 -mc.listen("onPistonTryPush", (pistionPos, bl) => { - if (pistionPos.dimid != PLAPI.getPlotWorldDimid()) return; - - const pushedBlockPos = bl.pos; // 尝试推动的方块位置 - - const sou = PLAPI.getPlotPosByPos(pistionPos); - const tar = PLAPI.getPlotPosByPos(pushedBlockPos); - - if (sou.isValid() && tar.isValid() && !tar.isPosOnBorder(pushedBlockPos)) - return; // 地皮内 & 地皮外 & 非边框 => 放行 - - if (!sou.isValid() && !tar.isValid() && !tar.isPosOnBorder(pushedBlockPos)) - return; // 地皮外 & 地皮外 & 非边框 => 放行 - - return false; -}); - -// 生物受伤 -mc.listen("onMobHurt", (mob, sou, dmg, cause) => { - const mobPos = mob.pos; // 受伤的生物位置 - if (mobPos.dimid != PLAPI.getPlotWorldDimid()) return; - - const pps = PLAPI.getPlotPosByPos(mobPos); - const tab = new PlotMetadata(pps.getPlotID).getPermissionTableConst(); - - const valid = pps.isValid(); - if (!valid) return; // 地皮外 => 放行 - - if (sou && sou.isPlayer()) { - const pl = sou.toPlayer(); - const lv = PLAPI.getPlayerPermission(pl.uuid, pps.getPlotID()); - - if (lv >= 1) return; // 管理员 & 所有者 & 成员 => 放行 - } - - if (!tab) return; // 全新地皮 => 放行 - if (!tab.canMobHurt) return false; // 地皮禁止生物受伤 => 拦截 -}); - -// ====================================================================== -// 以下为 地皮插件自定义事件测试代码 - -// PLEvent.on( -// "PlayerEnterPlot", -// /** -// * @param {Player} player -// * @param {Array} PlotPosConstructorArgs -// */ -// (player, PlotPosConstructorArgs) => { -// // 使用事件提供的参数构造 PlotPos 对象 -// // 受 RemoteCall 限制,只能导出参数在Js构造虚对象 -// const pps = new PlotPos(PlotPosConstructorArgs); -// logger.info(`玩家: ${player.realName} 进入地皮: ${pps.toString()}`); -// } -// ); - -// PLEvent.on( -// "PlayerLeavePlot", -// /** -// * @param {Player} player -// * @param {Array} PlotPosConstructorArgs -// */ -// (player, PlotPosConstructorArgs) => { -// const pps = new PlotPos(PlotPosConstructorArgs); -// logger.info(`玩家: ${player.realName} 离开地皮: ${pps.toString()}`); -// } -// ); diff --git a/tooth.json b/tooth.json index 6543686..7ec1501 100644 --- a/tooth.json +++ b/tooth.json @@ -1,7 +1,7 @@ { "format_version": 2, "tooth": "github.com/engsr6982/PlotCraft", - "version": "0.6.1", + "version": "1.0.0", "info": { "name": "PlotCraft - 地皮插件", "description": "地皮世界|地皮购买|地皮出售|地皮共享|地皮评论", @@ -9,16 +9,16 @@ "tags": [ "levilamina", "plugin", + "mod", "plot", "plotcraft" ] }, - "asset_url": "https://github.com/engsr6982/PlotCraft/releases/download/v0.6.1/PlotCraft-windows-x64.zip", + "asset_url": "https://github.com/engsr6982/PlotCraft/releases/download/v1.0.0/PlotCraft-windows-x64.zip", "prerequisites": { - "github.com/LiteLDev/LeviLamina": "0.13.4", + "github.com/LiteLDev/LeviLamina": ">=0.13.4 <0.14.0", "github.com/LiteLDev/MoreDimensions": "0.4.1", - "github.com/LiteLDev/LegacyMoney": "0.8.2", - "github.com/LiteLDev/LegacyRemoteCall": "0.8.2" + "github.com/LiteLDev/LegacyMoney": ">=0.8.2" }, "files": { "place": [ diff --git a/xmake.lua b/xmake.lua index 2f7cfbb..0adb0ed 100644 --- a/xmake.lua +++ b/xmake.lua @@ -7,7 +7,6 @@ add_repositories("liteldev-repo https://github.com/LiteLDev/xmake-repo.git") -- please note that you should add bdslibrary yourself if using dev version add_requires( "levilamina 0.13.5", - "sqlitecpp 3.2.1", "legacymoney 0.8.3" ) @@ -19,21 +18,9 @@ if get_config("overworld") == false then add_requires("more-dimensions 0.4.1") end -if get_config("remote") == true then - add_requires("legacyremotecall 0.8.3") -end - -option("gen") -- Generator - set_default(1) - set_values(1, 2) - option("overworld") -- Overworld set_default(false) -option("remote") -- RemoteCall - set_default(true) - - target("PlotCraft") add_cxflags( "/EHa", @@ -54,7 +41,6 @@ target("PlotCraft") ) add_packages( "levilamina", - "sqlitecpp", "legacymoney" ) add_files("src/**.cpp", "src/**.cc") @@ -68,11 +54,6 @@ target("PlotCraft") set_languages("c++20") set_symbols("debug") - -- GEN_1 - -- GEN_2 - -- OVERWORLD - -- REMOTE_API - if is_mode("debug") then add_defines("DEBUG") end @@ -83,18 +64,7 @@ target("PlotCraft") add_defines("OVERWORLD") end - if get_config("remote") == true then - add_packages("legacyremotecall") - add_defines("REMOTE_API") - end - - if get_config("gen") == 1 then - add_defines("GEN_1") - else - add_defines("GEN_2") - end - - add_defines("BuildVersionInfo=\"PlotGenerator: " .. tostring(get_config("gen")) .. " | Overworld: " .. tostring(get_config("overworld")) .. " | RemoteCall: " .. tostring(get_config("remote")) .. "\"") + add_defines("BuildVersionInfo=\"Overworld: " .. tostring(get_config("overworld")) .. "\"") add_defines("PLUGIN_NAME=\"PlotCraft\"") add_defines("PLUGIN_TITLE=\"§6[§aPlotCraft§6]§r \"") @@ -102,7 +72,7 @@ target("PlotCraft") after_build(function (target) local plugin_packer = import("scripts.after_build") - cprint("${bright green}[Build Infomation]: ${reset}PlotGenerator: " .. tostring(get_config("gen")) .. " | Overworld: " .. tostring(get_config("overworld")) .. " | RemoteCall: " .. tostring(get_config("remote"))) + cprint("${bright green}[Build Infomation]: ${reset}Overworld: " .. tostring(get_config("overworld"))) local tag = os.iorun("git describe --tags --abbrev=0 --always") local major, minor, patch, suffix = tag:match("v(%d+)%.(%d+)%.(%d+)(.*)")